diff --git a/composer.json b/composer.json index a12471c..4216ab0 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,8 @@ }, "require": { "b13/container": "^4.1", + "ep/ep-products": "@dev", + "ep/ep-theme": "@dev", "ssch/typo3-encore": "^7.0", "typo3/cms-backend": "^13.4", "typo3/cms-belog": "^13.4", diff --git a/composer.lock b/composer.lock index 5d369cb..3b28889 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d4af0a677424f63a718c41200397902d", + "content-hash": "360f5f33d266a5b828de7280a438b5ce", "packages": [ { "name": "b13/container", @@ -816,6 +816,92 @@ }, "time": "2026-10-20T09:45:10+00:00" }, + { + "name": "ep/ep-products", + "version": "dev-master", + "dist": { + "type": "path", + "url": "./packages/ep_products", + "reference": "291793cfe2a2d5f50bf5bee13f286e501d633a49" + }, + "require": { + "league/oauth2-client": "^2.9", + "league/period": "^4.12", + "php": "^8.2", + "symfony/http-client": "^7.4", + "typo3/cms-core": "^13.4", + "typo3/cms-extbase": "^13.4", + "typo3/cms-fluid": "^13.4", + "typo3/cms-frontend": "^13.4" + }, + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "extension-key": "ep_products" + } + }, + "autoload": { + "psr-4": { + "EP\\EpProducts\\": "Classes/" + } + }, + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Björn Fromme", + "email": "fromme@dreipunktnull.com" + } + ], + "description": "Implementation of all product related logic and models", + "transport-options": { + "relative": true + } + }, + { + "name": "ep/ep-theme", + "version": "dev-master", + "dist": { + "type": "path", + "url": "./packages/ep_theme", + "reference": "f3e425fca030011066b3d2e6a25a756941885b0d" + }, + "require": { + "b13/container": "^4.1", + "ep/ep-products": "@dev", + "php": "^8.2", + "typo3/cms-core": "^13.4", + "typo3/cms-extbase": "^13.4", + "typo3/cms-fluid": "^13.4", + "typo3/cms-fluid-styled-content": "^13.4", + "typo3/cms-frontend": "^13.4" + }, + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "extension-key": "ep_theme" + } + }, + "autoload": { + "psr-4": { + "EP\\EpTheme\\": "Classes/" + } + }, + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Björn Fromme", + "email": "fromme@dreipunktnull.com" + } + ], + "description": "Theme package for Ep Reisen TYPO3 website", + "transport-options": { + "relative": true + } + }, { "name": "firebase/php-jwt", "version": "v7.2.1", @@ -1219,6 +1305,157 @@ ], "time": "2026-10-17T23:57:19+00:00" }, + { + "name": "league/oauth2-client", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "8cedfef9d01a8d1fd2ecfabb41734b17592c4b71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/8cedfef9d01a8d1fd2ecfabb41734b17592c4b71", + "reference": "8cedfef9d01a8d1fd2ecfabb41734b17592c4b71", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.8.2 || ^8.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.6.3 || ^3.0", + "php": "^7.1 || >=8.0.0 <8.6.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.9.1" + }, + "time": "2026-09-16T13:14:31+00:00" + }, + { + "name": "league/period", + "version": "4.12.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/period.git", + "reference": "766e9dddabbbef7d9655efd857ce977f8541b596" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/period/zipball/766e9dddabbbef7d9655efd857ce977f8541b596", + "reference": "766e9dddabbbef7d9655efd857ce977f8541b596", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.2.0 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.19 || ~v3.4.0", + "phpstan/phpstan": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.0.0", + "phpstan/phpstan-strict-rules": "^1.0.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "League\\Period\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://github.com/nyamsprod/", + "role": "Developer" + } + ], + "description": "Time range API for PHP", + "homepage": "http://period.thephpleague.com", + "keywords": [ + "boundaries", + "calendar", + "collection", + "date", + "dateinterval", + "dateperiod", + "datetime", + "gap", + "intersections", + "interval", + "period", + "range", + "schedule", + "sequence", + "time", + "timeline", + "unions" + ], + "support": { + "docs": "https://period.thephpleague.com", + "issues": "https://github.com/thephpleague/period/issues", + "source": "https://github.com/thephpleague/period" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2022-02-21T08:41:21+00:00" + }, { "name": "lolli42/finediff", "version": "1.1.2", @@ -3420,6 +3657,189 @@ ], "time": "2026-09-26T06:31:10+00:00" }, + { + "name": "symfony/http-client", + "version": "v7.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "121bf3a973bfd852db85ec32d0d64e9fbed4449b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/121bf3a973bfd852db85ec32d0d64e9fbed4449b", + "reference": "121bf3a973bfd852db85ec32d0d64e9fbed4449b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-10-13T15:31:38+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.7.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/35be0019e2c2c9fba80f9dc033290a5240f7b44f", + "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-04T08:41:16+00:00" + }, { "name": "symfony/http-foundation", "version": "v7.4.21", @@ -9409,7 +9829,10 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "ep/ep-products": 20, + "ep/ep-theme": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": {}, diff --git a/packages/ep_products/Classes/BpnConnect/ApiClient.php b/packages/ep_products/Classes/BpnConnect/ApiClient.php new file mode 100644 index 0000000..68fc1b8 --- /dev/null +++ b/packages/ep_products/Classes/BpnConnect/ApiClient.php @@ -0,0 +1,140 @@ + $hotelCode, + 'dateFrom' => $dateFrom->format('Y-m-d'), + 'mode' => $mode, + ]; + + if (null !== $dateTo) { + $query['dateTo'] = $dateTo->format('Y-m-d'); + } + + $response = $this->request('GET', 'api/v1/contingents/calendar', [ + 'query' => $query, + ]); + + $this->assertStatusCode($response, [Response::HTTP_OK], 'GET', 'api/v1/contingents/calendar'); + + return $this->decodeResponse($response, 'GET', 'api/v1/contingents/calendar'); + } + + private function request(string $method, string $uri, array $options = []): ResponseInterface + { + try { + return $this->getHttpClient()->request($method, $uri, $options); + } catch (TransportExceptionInterface $e) { + throw new ApiException(sprintf('Bpn Connect API transport error for %s %s: %s', $method, $uri, $e->getMessage()), 0, $e); + } + } + + private function assertStatusCode(ResponseInterface $response, array $expectedStatusCodes, string $method, string $uri): void + { + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new ApiException(sprintf('Bpn Connect API transport error while reading status for %s %s: %s', $method, $uri, $e->getMessage()), 0, $e); + } + + if (in_array($statusCode, $expectedStatusCodes, true)) { + return; + } + + throw new ApiException(sprintf('Bpn Connect API returned unexpected status %d for %s %s', $statusCode, $method, $uri), $statusCode); + } + + private function decodeResponse(ResponseInterface $response, string $method, string $uri): array + { + try { + $data = $response->toArray(false); + } catch (DecodingExceptionInterface $e) { + throw new ApiException(sprintf('Bpn Connect API returned invalid JSON for %s %s: %s', $method, $uri, $e->getMessage()), 0, $e); + } catch (TransportExceptionInterface $e) { + throw new ApiException(sprintf('Bpn Connect API transport error while reading body for %s %s: %s', $method, $uri, $e->getMessage()), 0, $e); + } + + if (!isset($data['data']) || !is_array($data['data'])) { + throw new ApiException(sprintf('Bpn Connect API returned a calendar payload without a data array for %s %s', $method, $uri)); + } + + return $data; + } + + private function getHttpClient(): HttpClientInterface + { + $config = $this->getConfiguration(); + $baseUrl = trim((string)($config['bpnConnectApiBaseUrl'] ?? '')); + $apiKey = trim((string)($config['bpnConnectApiKey'] ?? '')); + + if ($baseUrl === '') { + throw new ApiException('Bpn Connect API base URL is not configured.'); + } + if ($apiKey === '') { + throw new ApiException('Bpn Connect API key is not configured.'); + } + + $options = [ + 'headers' => [ + 'X-API-KEY' => $apiKey, + ], + ]; + + // Without these the client falls back to PHP's default_socket_timeout (commonly 60s), + // so a stalled API pins a PHP worker for a minute per request. The defaults live in + // code rather than in ext_conf_template.txt: ExtensionConfiguration::get() returns the + // stored configuration verbatim and only syncs the template when an extension has no + // configuration at all, so a newly added template key is absent until someone saves + // extension configuration by hand. Falling back to 0 there would silently mean "no + // timeout" - the opposite of what this guard is for. An explicit 0 still disables. + $timeout = (int)($config['bpnConnectApiTimeout'] ?? self::DEFAULT_TIMEOUT); + if ($timeout > 0) { + $options['timeout'] = $timeout; + } + $maxDuration = (int)($config['bpnConnectApiMaxDuration'] ?? self::DEFAULT_MAX_DURATION); + if ($maxDuration > 0) { + $options['max_duration'] = $maxDuration; + } + + return HttpClient::createForBaseUri(rtrim($baseUrl, '/') . '/', $options); + } + + private function getConfiguration(): array + { + if (null === $this->configuration) { + $this->configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); + } + + return $this->configuration; + } +} diff --git a/packages/ep_products/Classes/BpnConnect/ApiException.php b/packages/ep_products/Classes/BpnConnect/ApiException.php new file mode 100644 index 0000000..8676702 --- /dev/null +++ b/packages/ep_products/Classes/BpnConnect/ApiException.php @@ -0,0 +1,7 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\ContingentImportService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; + +class ContingentCommand extends Command +{ + + /** + * @var ContingentImportService + */ + protected $contingentImportService; + + /** + * @var string + */ + protected static $defaultName = 'ep:contingent-import'; + + /** + * @param ContingentImportService $contingentImportService + */ + public function __construct() + { + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $this->contingentImportService = $objectManager->get(ContingentImportService::class); + + parent::__construct(); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws \Doctrine\DBAL\DBALException + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $count = $this->contingentImportService->import(); + $output->writeln(sprintf('%d rows imported.', $count )); + + return 0; + } + +} diff --git a/packages/ep_products/Classes/Command/DateCommandController.php b/packages/ep_products/Classes/Command/DateCommandController.php new file mode 100644 index 0000000..a530858 --- /dev/null +++ b/packages/ep_products/Classes/Command/DateCommandController.php @@ -0,0 +1,120 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\DateImportService; +use EP\EpProducts\Service\ProductImportSourceFactory; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; +use TYPO3\CMS\Extbase\Service\CacheService; + +class DateCommandController extends Command +{ + + /** + * @var \EP\EpProducts\Service\DateImportService + */ + protected $dateImportService; + + /** + * @var CacheService + */ + protected $cacheService; + + /** + * @var ConfigurationManagerInterface + */ + protected $configurationManager; + + /** + * @var ProductImportSourceFactory + */ + protected $importSourceFactory; + + /** + * @param DateImportService $importService + * @param CacheService $cacheService + * @param ConfigurationManagerInterface $configurationManager + * @param ProductImportSourceFactory $importSourceFactory + */ + public function __construct + ( + DateImportService $importService, + CacheService $cacheService, + ConfigurationManagerInterface $configurationManager, + ProductImportSourceFactory $importSourceFactory + ) + { + $this->dateImportService = $importService; + $this->cacheService = $cacheService; + $this->configurationManager = $configurationManager; + $this->importSourceFactory = $importSourceFactory; + + parent::__construct(); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws \Doctrine\DBAL\DBALException + */ + public function execute(InputInterface $input, OutputInterface $output): int + { + $importSource = $this->importSourceFactory->get(); + $import = $importSource->acquire(); + if ($import === null) { + $output->writeln('No new import available.'); + return 0; + } + + $count = $this->dateImportService->import($import->getRows()); + $importSource->markImported($import); + $output->writeln(sprintf('%d rows imported.', $count )); + $settings = $this->getSettings(); + $resellerPageUid = $settings['resellerExportPageUid']; + $travelInfoPageUid = $settings['travelInfoPageUid']; + $this->cacheService->clearPageCache([$resellerPageUid, $travelInfoPageUid]); + + return 0; + } + + /** + * @return array + */ + protected function getSettings(): array + { + $settings = $this + ->configurationManager + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); + + return $settings['plugin.']['tx_epproducts.']['settings.']; + } + +} diff --git a/packages/ep_products/Classes/Command/ExportCommandController.php b/packages/ep_products/Classes/Command/ExportCommandController.php new file mode 100644 index 0000000..62c94e6 --- /dev/null +++ b/packages/ep_products/Classes/Command/ExportCommandController.php @@ -0,0 +1,178 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\CsvFormatter\FacebookCatalogCsvFormatter; +use EP\EpProducts\CsvFormatter\GoogleBusinessFeedCsvFormatter; +use EP\EpProducts\Domain\Model\Reseller; +use EP\EpProducts\Domain\Repository\ResellerRepository; +use EP\EpProducts\Service\ResellerDataService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Serializer; +use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; + +class ExportCommandController extends Command +{ + /** + * @var array + */ + protected static $imageDimensions = [ + 'facebook' => [ + 'width' => '1080', + 'maxHeight' => '1080', + 'cropVariant' => 'facebook', + ], + 'google' => [ + 'width' => '1200', + 'maxHeight' => '1200', + 'cropVariant' => 'google', + ] + ]; + + /** + * @var ResellerRepository + */ + protected $resellerRepository; + + /** + * @var ResellerDataService + */ + protected $resellerDataService; + + public function __construct(string $name = null) + { + parent::__construct($name); + + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $this->resellerRepository = $objectManager->get(ResellerRepository::class); + $this->resellerDataService = $objectManager->get(ResellerDataService::class); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws \Doctrine\DBAL\DBALException + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $resellers = $this->resellerRepository->findAll(); + $destinationFolder = sprintf( + '%s/fileadmin/reseller', + \TYPO3\CMS\Core\Core\Environment::getPublicPath() + ); + if (!file_exists($destinationFolder)) { + mkdir($destinationFolder, 0755); + } + foreach ($resellers as $reseller) { + /** @var Reseller $reseller */ + $output->writeln('Exporting XML for ' . $reseller->getName()); + $this->exportXml($reseller, $destinationFolder); + $output->writeln('Exporting CSV (Facebook) for ' . $reseller->getName()); + $this->exportCsv($reseller, 'facebook', $destinationFolder); + $output->writeln('Exporting CSV (Google) for ' . $reseller->getName()); + $this->exportCsv($reseller, 'google', $destinationFolder); + } + + return 0; + } + + /** + * @param Reseller $reseller + * @param string $destinationFolder + * @throws \Doctrine\DBAL\DBALException + */ + protected function exportXml(Reseller $reseller, $destinationFolder) + { + $myEpUrl = $this->getMyEpUrl(); + $data = $this->resellerDataService->preprocessAll($reseller, $myEpUrl); + $encoders = [new XmlEncoder('products')]; + $normalizers = [new ObjectNormalizer()]; + $serializer = new Serializer($normalizers, $encoders); + $xml = $serializer->serialize( + $data, + 'xml', + [ 'xml_encoding' => 'utf-8', 'xml_format_output' => false ] + ); + $filename = sprintf( + '%s/export_%s.xml', + $destinationFolder, + $reseller->getCode() + ); + @unlink($filename); + file_put_contents($filename, $xml); + } + + /** + * @param Reseller $reseller + * @param string $type + * @param string $destinationFolder + * @throws \Doctrine\DBAL\DBALException + */ + protected function exportCsv(Reseller $reseller, $type, $destinationFolder) + { + $myEpUrl = $this->getMyEpUrl(); + $data = $this->resellerDataService->preprocessAll( + $reseller, + $myEpUrl, + static::$imageDimensions[$type], + $type + ); + if ($type === 'google') { + $csv = GoogleBusinessFeedCsvFormatter::format($data); + } else { + $csv = FacebookCatalogCsvFormatter::format($data); + } + $filename = sprintf( + '%s/export_%s_%s.csv', + $destinationFolder, + $reseller->getCode(), + $type + ); + @unlink($filename); + $fileHandle = fopen($filename, 'wb'); + foreach ($csv as $row) { + fputcsv($fileHandle, $row); + } + fclose($fileHandle); + } + + protected function getMyEpUrl(): string + { + $config = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); + + return $config['myEpUrl'] ?? 'https://my.ep-reisen.de'; + } +} diff --git a/packages/ep_products/Classes/Command/GroupsSwissCommand.php b/packages/ep_products/Classes/Command/GroupsSwissCommand.php new file mode 100644 index 0000000..cbe9888 --- /dev/null +++ b/packages/ep_products/Classes/Command/GroupsSwissCommand.php @@ -0,0 +1,85 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\GroupsSwissService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; + +class GroupsSwissCommand extends Command +{ + /** + * @var GroupsSwissService + */ + private $groupsSwissService; + + protected static $defaultName = 'ep:groups-swiss'; + + public function __construct() + { + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $this->groupsSwissService = $objectManager->get(GroupsSwissService::class); + + parent::__construct(); + } + + protected function configure() + { + $this->addArgument( + 'hotel', + InputArgument::REQUIRED, + 'E&P hotel uid' + ); + $this->addArgument( + 'house', + InputArgument::REQUIRED, + 'Groups.swiss house number' + ); + $this->addArgument( + 'key', + InputArgument::REQUIRED, + 'Groups.swiss API key' + ); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $hotelUid = $input->getArgument('hotel'); + $houseNumber = $input->getArgument('house'); + $apiKey = $input->getArgument('key'); + + $success = $this->groupsSwissService->publishContingents($hotelUid, $houseNumber, $apiKey); + + return $success ? 0 : 1; + } +} diff --git a/packages/ep_products/Classes/Command/SnowreportCommandController.php b/packages/ep_products/Classes/Command/SnowreportCommandController.php new file mode 100644 index 0000000..0efe92b --- /dev/null +++ b/packages/ep_products/Classes/Command/SnowreportCommandController.php @@ -0,0 +1,62 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\SnowreportImportService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class SnowreportCommandController extends Command +{ + + /** + * @var SnowreportImportService + */ + protected $snowreportImportService; + + /** + * @param SnowreportImportService $snowreportImportService + */ + public function __construct(SnowreportImportService $snowreportImportService) + { + $this->snowreportImportService = $snowreportImportService; + + parent::__construct(); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $this->snowreportImportService->downloadReportsXml(); + $count = $this->snowreportImportService->importSnowReportData(); + $output->writeln(sprintf('%d rows imported.', $count )); + + return 0; + } + +} diff --git a/packages/ep_products/Classes/ContentFeed/ContentFeedApi.php b/packages/ep_products/Classes/ContentFeed/ContentFeedApi.php new file mode 100644 index 0000000..230fd82 --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/ContentFeedApi.php @@ -0,0 +1,165 @@ +authenticator = $authenticator; + $this->configuration = $configuration; + $this->manifestBuilder = $manifestBuilder; + $this->recordSerializer = $recordSerializer; + $this->cache = $cacheManager->getCache('ep_products_cache'); + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + if (!$this->authenticator->isAuthorized($request)) { + return $this->error('Invalid or missing api key', 403); + } + + if (!$this->configuration->isEnabled()) { + return self::disabledResponse(); + } + + if ([] === $this->configuration->getSites()) { + // An empty feed would read as "everything was deleted". Refuse instead. + return $this->error('Content feed has no sites configured', 503); + } + + $path = substr($request->getUri()->getPath(), strlen(self::PATH_PREFIX)); + + if ('manifest' === $path) { + return new JsonResponse($this->getManifest(), 200, [], self::JSON_FLAGS); + } + + if ('records' === $path) { + return $this->handleRecordsRequest($request); + } + + return $this->error('Not found', 404); + } + + /** + * Uids that are not (or no longer) eligible are left out rather than answered with an error: + * the manifest may have changed between the consumer's two calls, and it reads a requested + * but absent uid as deleted. + */ + private function handleRecordsRequest(ServerRequestInterface $request): ResponseInterface + { + $type = (string) ($request->getQueryParams()['type'] ?? ''); + + if (!isset(RecordFinder::TYPES[$type])) { + return $this->error('Unknown type, expected one of: ' . implode(', ', array_keys(RecordFinder::TYPES)), 400); + } + + $uids = array_values(array_unique(array_filter( + GeneralUtility::intExplode(',', (string) ($request->getQueryParams()['uids'] ?? ''), true), + function (int $uid): bool { + return $uid > 0; + } + ))); + + if ([] === $uids) { + return $this->error('No uids given', 400); + } + + if (count($uids) > self::MAX_RECORDS_PER_REQUEST) { + return $this->error(sprintf('At most %d uids per request', self::MAX_RECORDS_PER_REQUEST), 400); + } + + return new JsonResponse([ + 'type' => $type, + 'items' => $this->recordSerializer->serialize($type, $uids), + ], 200, [], self::JSON_FLAGS); + } + + private function getManifest(): array + { + $manifest = $this->cache->get(self::MANIFEST_CACHE_IDENTIFIER); + + if (!is_array($manifest)) { + $manifest = $this->manifestBuilder->build(); + $this->cache->set(self::MANIFEST_CACHE_IDENTIFIER, $manifest, [], self::MANIFEST_CACHE_LIFETIME); + } + + return $manifest; + } + + /** + * The kill switch: contentFeedEnabled = 0 in the extension configuration, for when the host + * is struggling. The consumer stops its run, keeps everything it has and tries again later - + * nothing is deleted on the chatbot side. + */ + public static function disabledResponse(): ResponseInterface + { + return new JsonResponse( + ['message' => 'Content feed is switched off'], + 503, + ['Retry-After' => (string) self::DISABLED_RETRY_AFTER], + self::JSON_FLAGS + ); + } + + private function error(string $message, int $status): ResponseInterface + { + return new JsonResponse(['message' => $message], $status, [], self::JSON_FLAGS); + } +} diff --git a/packages/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php b/packages/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php new file mode 100644 index 0000000..342485a --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php @@ -0,0 +1,75 @@ +configuration = (array) $extensionConfiguration->get('ep_products'); + } + + public function isEnabled(): bool + { + return (bool) ($this->configuration['contentFeedEnabled'] ?? true); + } + + /** + * @return string[] site identifiers + */ + public function getSites(): array + { + return GeneralUtility::trimExplode(',', (string) ($this->configuration['contentFeedSites'] ?? ''), true); + } + + /** + * @return int[] language ids; the default language when unset + */ + public function getLanguages(): array + { + $languages = GeneralUtility::intExplode(',', (string) ($this->configuration['contentFeedLanguages'] ?? '0'), true); + + return [] === $languages ? [0] : $languages; + } + + /** + * @return int[] page uids whose whole subtree stays out of the feed + */ + public function getExcludedPages(): array + { + return GeneralUtility::intExplode(',', (string) ($this->configuration['contentFeedExcludedPages'] ?? ''), true); + } + + /** + * @return array site identifier => news detail page uid + */ + public function getNewsDetailPages(): array + { + $pages = []; + + foreach (GeneralUtility::trimExplode(',', (string) ($this->configuration['contentFeedNewsDetailPages'] ?? ''), true) as $entry) { + $parts = GeneralUtility::trimExplode(':', $entry, true); + + if (2 === count($parts) && (int) $parts[1] > 0) { + $pages[$parts[0]] = (int) $parts[1]; + } + } + + return $pages; + } +} diff --git a/packages/ep_products/Classes/ContentFeed/ManifestBuilder.php b/packages/ep_products/Classes/ContentFeed/ManifestBuilder.php new file mode 100644 index 0000000..32fe25c --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/ManifestBuilder.php @@ -0,0 +1,379 @@ + ['uid', 'tstamp', 'detail_page'], + 'hotel' => ['uid', 'tstamp', 'detail_page'], + 'region' => ['uid', 'tstamp', 'detail_page'], + 'country' => ['uid', 'tstamp', 'detail_page'], + 'city' => ['uid', 'tstamp', 'detail_page'], + 'concept' => ['uid', 'tstamp', 'detail_page'], + 'news' => ['uid', 'tstamp', 'pid'], + ]; + + /** + * @var PageTree + */ + private $pageTree; + + /** + * @var UrlResolver + */ + private $urlResolver; + + /** + * @var RecordFinder + */ + private $recordFinder; + + /** + * @var ContentFeedConfiguration + */ + private $configuration; + + /** + * @var ConnectionPool + */ + private $connectionPool; + + public function __construct( + PageTree $pageTree, + UrlResolver $urlResolver, + RecordFinder $recordFinder, + ContentFeedConfiguration $configuration, + ConnectionPool $connectionPool + ) { + $this->pageTree = $pageTree; + $this->urlResolver = $urlResolver; + $this->recordFinder = $recordFinder; + $this->configuration = $configuration; + $this->connectionPool = $connectionPool; + } + + public function build(): array + { + $sites = $this->urlResolver->getIncludedSites(); + $records = $this->buildRecordItems(); + + return [ + 'generatedAt' => date('c'), + 'sites' => array_map([$this, 'describeSite'], $sites), + 'items' => array_merge($this->buildPageItems($sites, $this->collectDetailPages()), $records), + ]; + } + + private function describeSite(Site $site): array + { + $languages = []; + + foreach ($site->getLanguages() as $language) { + $languages[] = $language->getTwoLetterIsoCode(); + } + + $title = trim((string) ($site->getConfiguration()['websiteTitle'] ?? '')); + + if ('' === $title) { + $title = trim($site->getDefaultLanguage()->getWebsiteTitle()); + } + + return [ + 'identifier' => $site->getIdentifier(), + 'title' => '' !== $title ? $title : $site->getIdentifier(), + 'base' => (string) $site->getBase(), + 'languages' => $languages, + ]; + } + + /** + * @param Site[] $sites + * @param array $detailPages detailOf entries by page uid + */ + private function buildPageItems(array $sites, array $detailPages): array + { + $content = $this->findContentStatistics(); + $excluded = array_flip($this->configuration->getExcludedPages()); + $feedLanguages = $this->configuration->getLanguages(); + $items = []; + + foreach ($sites as $site) { + $languageIds = []; + + foreach ($site->getLanguages() as $language) { + if (in_array($language->getLanguageId(), $feedLanguages, true)) { + $languageIds[$language->getLanguageId()] = $language->getTwoLetterIsoCode(); + } + } + + $context = [ + 'site' => $site, + 'languages' => $languageIds, + 'excluded' => $excluded, + 'content' => $content, + 'detailPages' => $detailPages, + ]; + $breadcrumbs = array_fill_keys(array_keys($languageIds), []); + + $this->walk($site->getRootPageId(), $context, $breadcrumbs, true, $items); + } + + return $items; + } + + /** + * @param array $breadcrumbs titles above this page, by language id + */ + private function walk(int $uid, array $context, array $breadcrumbs, bool $isRoot, array &$items): void + { + $page = $this->pageTree->getPage($uid); + + if (null === $page || isset($context['excluded'][$uid])) { + return; + } + + if (in_array((int) $page['doktype'], self::DOKTYPES_NOT_TRAVERSED, true)) { + return; + } + + $visible = $this->pageTree->isVisible($page); + + if (!$visible && (int) $page['extendToSubpages'] === 1) { + return; + } + + $childBreadcrumbs = $breadcrumbs; + + foreach ($context['languages'] as $languageId => $isoCode) { + $row = $this->getLanguageRow($page, $languageId); + + if ($visible && null !== $row && $this->isPublishable($page, $row, $languageId)) { + $item = $this->buildPageItem($page, $row, $languageId, $isoCode, $breadcrumbs[$languageId], $context); + + if (null !== $item) { + $items[] = $item; + } + } + + if (!$isRoot) { + $source = $row ?? $page; + $childBreadcrumbs[$languageId][] = '' !== trim((string) $source['nav_title']) + ? trim((string) $source['nav_title']) + : trim((string) $source['title']); + } + } + + foreach ($this->pageTree->getChildren($uid) as $childUid) { + $this->walk($childUid, $context, $childBreadcrumbs, false, $items); + } + } + + private function getLanguageRow(array $page, int $languageId): ?array + { + if (0 === $languageId) { + return $page; + } + + $overlay = $this->pageTree->getOverlay((int) $page['uid'], $languageId); + + return null !== $overlay && $this->pageTree->isVisible($overlay) ? $overlay : null; + } + + /** + * Mirrors the XML sitemap: standard pages only, nothing marked no_index. If Google is not + * meant to see a page, the chatbot is not either. + */ + private function isPublishable(array $page, array $row, int $languageId): bool + { + if ((int) $page['doktype'] !== self::DOKTYPE_DEFAULT || (int) $row['no_index'] === 1) { + return false; + } + + return 0 !== $languageId || 0 === ((int) $page['l18n_cfg'] & self::L18N_HIDE_DEFAULT_LANGUAGE); + } + + private function buildPageItem(array $page, array $row, int $languageId, string $isoCode, array $breadcrumb, array $context): ?array + { + $uid = (int) $page['uid']; + // "Show content from page": the elements live on another page, and so do their changes. + // The site answers 404 when that page is hidden. + $contentPid = (int) $page['content_from_pid'] ?: $uid; + $contentPage = $this->pageTree->getPage($contentPid); + + if (null === $contentPage || !$this->pageTree->isVisible($contentPage)) { + return null; + } + + // Without a visible element other than plugins, the content-only render is empty (it + // leaves plugins out), so there is nothing to fetch. That is most product and hotel + // detail pages: their text comes through the records endpoint. + $content = $context['content'][$contentPid] ?? []; + $textElements = ($content[$languageId]['text'] ?? 0) + ($content[-1]['text'] ?? 0); + + if (0 === $textElements) { + return null; + } + + $url = $this->urlResolver->getPageUrl($uid, $languageId); + + if (null === $url) { + return null; + } + + $modified = max( + (int) $row['tstamp'], + $content[$languageId]['tstamp'] ?? 0, + $content[-1]['tstamp'] ?? 0 + ); + + return [ + 'type' => 'page', + 'uid' => $uid, + 'language' => $isoCode, + 'site' => $context['site']->getIdentifier(), + 'url' => $url, + 'contentUrl' => $this->urlResolver->getPageUrl($uid, $languageId, ['type' => self::CONTENT_PAGE_TYPE]), + 'title' => trim((string) $row['title']), + 'description' => $this->nullIfEmpty((string) $row['description']), + 'breadcrumb' => $breadcrumb, + 'detailOf' => $context['detailPages'][$uid] ?? [], + 'modified' => date('c', $modified), + ]; + } + + /** + * Per page and language: the latest change to any content element, and how many visible + * elements are not plugins. + * Deleted and hidden elements count towards the change - removing or hiding one changes the + * page - which is also why SYS_LASTCHANGED is no use here: it only moves on render. + * + * @return array> + */ + private function findContentStatistics(): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content'); + $queryBuilder->getRestrictions()->removeAll(); + + $rows = $queryBuilder + ->selectLiteral( + 'pid', + 'sys_language_uid', + 'MAX(tstamp) AS tstamp', + "SUM(CASE WHEN deleted = 0 AND hidden = 0 AND CType <> 'list' THEN 1 ELSE 0 END) AS text_elements" + ) + ->from('tt_content') + ->groupBy('pid', 'sys_language_uid') + ->execute() + ->fetchAllAssociative() + ; + + $statistics = []; + + foreach ($rows as $row) { + $statistics[(int) $row['pid']][(int) $row['sys_language_uid']] = [ + 'tstamp' => (int) $row['tstamp'], + 'text' => (int) $row['text_elements'], + ]; + } + + return $statistics; + } + + /** + * Which records name a page as their detail page. Keyed by the page the record points at, + * not by where a shortcut leads: a shortcut is not in the manifest, and the page it leads to + * usually describes something broader (product detail shortcuts point at region pages). + * + * @return array + */ + private function collectDetailPages(): array + { + $detailPages = []; + + foreach (self::RECORD_FIELDS as $type => $fields) { + if (!in_array('detail_page', $fields, true)) { + continue; + } + + foreach ($this->recordFinder->find($type, ['uid', 'detail_page']) as $uid => $row) { + $pageUid = $this->urlResolver->parsePageReference((string) $row['detail_page']); + + if (null !== $pageUid) { + $detailPages[$pageUid][] = ['type' => $type, 'uid' => $uid]; + } + } + } + + return $detailPages; + } + + private function buildRecordItems(): array + { + $faqTimestamps = $this->recordFinder->findFaqTimestamps(); + $matchcodeTimestamps = $this->recordFinder->findMatchcodeTimestamps(); + $items = []; + + foreach (self::RECORD_FIELDS as $type => $fields) { + foreach ($this->recordFinder->find($type, $fields) as $uid => $row) { + if ('news' === $type) { + $link = $this->urlResolver->getNewsLink((int) $row['uid'], (int) $row['pid']); + + // News has no detail_page of its own: an article stored outside the feed's + // sites (the retired ep-events) is not part of the feed at all. + if (null === $link) { + continue; + } + } else { + $link = $this->urlResolver->getRecordLink((string) $row['detail_page']); + } + + $modified = (int) $row['tstamp']; + + if ('product' === $type) { + $modified = max($modified, $faqTimestamps[$uid] ?? 0); + } + + if ('hotel' === $type) { + $modified = max($modified, $matchcodeTimestamps[$uid] ?? 0); + } + + $items[] = [ + 'type' => $type, + 'uid' => $uid, + 'language' => 'de', + 'site' => $link['site'], + 'url' => $link['url'], + 'modified' => date('c', $modified), + ]; + } + } + + return $items; + } + + private function nullIfEmpty(string $value): ?string + { + $value = trim($value); + + return '' === $value ? null : $value; + } +} diff --git a/packages/ep_products/Classes/ContentFeed/PageTree.php b/packages/ep_products/Classes/ContentFeed/PageTree.php new file mode 100644 index 0000000..b2d1b0c --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/PageTree.php @@ -0,0 +1,131 @@ +|null default-language rows by uid + */ + private $pages; + + /** + * @var array child uids by parent uid, in sorting order + */ + private $children = []; + + /** + * @var array> overlay rows by default-language uid and language id + */ + private $overlays = []; + + public function __construct(ConnectionPool $connectionPool, Context $context) + { + $this->connectionPool = $connectionPool; + $this->now = (int) $context->getPropertyFromAspect('date', 'timestamp'); + } + + public function getPage(int $uid): ?array + { + $this->load(); + + return $this->pages[$uid] ?? null; + } + + public function getOverlay(int $uid, int $languageId): ?array + { + $this->load(); + + return $this->overlays[$uid][$languageId] ?? null; + } + + /** + * @return int[] + */ + public function getChildren(int $uid): array + { + $this->load(); + + return $this->children[$uid] ?? []; + } + + /** + * Visible to an anonymous frontend visitor: not hidden, inside its start/end window, and not + * restricted to a frontend user group. + */ + public function isVisible(array $row): bool + { + if ((int) $row['hidden'] === 1) { + return false; + } + + if ((int) $row['starttime'] > $this->now) { + return false; + } + + if ((int) $row['endtime'] !== 0 && (int) $row['endtime'] <= $this->now) { + return false; + } + + return in_array((string) $row['fe_group'], ['', '0'], true); + } + + private function load(): void + { + if (null !== $this->pages) { + return; + } + + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions()->removeAll()->add(new DeletedRestriction()); + + $rows = $queryBuilder + ->select(...self::FIELDS) + ->from('pages') + ->orderBy('sorting') + ->execute() + ->fetchAllAssociative() + ; + + $this->pages = []; + + foreach ($rows as $row) { + $uid = (int) $row['uid']; + + if ((int) $row['sys_language_uid'] > 0) { + $this->overlays[(int) $row['l10n_parent']][(int) $row['sys_language_uid']] = $row; + continue; + } + + $this->pages[$uid] = $row; + $this->children[(int) $row['pid']][] = $uid; + } + } +} diff --git a/packages/ep_products/Classes/ContentFeed/RecordFinder.php b/packages/ep_products/Classes/ContentFeed/RecordFinder.php new file mode 100644 index 0000000..a69c1f8 --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/RecordFinder.php @@ -0,0 +1,152 @@ + 'tx_epproducts_domain_model_product', + 'hotel' => 'tx_epproducts_domain_model_hotel', + 'region' => 'tx_epproducts_domain_model_region', + 'country' => 'tx_epproducts_domain_model_country', + 'city' => 'tx_epproducts_domain_model_city', + 'concept' => 'tx_epproducts_domain_model_concept', + 'news' => 'tx_news_domain_model_news', + ]; + + /** + * @var ConnectionPool + */ + private $connectionPool; + + public function __construct(ConnectionPool $connectionPool) + { + $this->connectionPool = $connectionPool; + } + + /** + * @param string[] $fields + * @param int[]|null $uids null for all eligible records + * @return array rows by uid, in uid order + */ + public function find(string $type, array $fields, ?array $uids = null): array + { + $table = self::TYPES[$type]; + $queryBuilder = $this->connectionPool->getQueryBuilderForTable($table); + $queryBuilder + ->select(...$fields) + ->from($table) + ->where($queryBuilder->expr()->in('sys_language_uid', [0, -1])) + ->orderBy('uid') + ; + + if (null !== $uids) { + if ([] === $uids) { + return []; + } + + $queryBuilder->andWhere($queryBuilder->expr()->in( + 'uid', + $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY) + )); + } + + $this->addTypeConstraints($type, $queryBuilder); + + $rows = []; + + foreach ($queryBuilder->execute()->fetchAllAssociative() as $row) { + $rows[(int) $row['uid']] = $row; + } + + return $rows; + } + + /** + * Latest change to a product's FAQs, by product uid. Deleted and hidden FAQs count: removing + * one is a change to the product's text. + * + * @return array + */ + public function findFaqTimestamps(): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_faq'); + $queryBuilder->getRestrictions()->removeAll(); + + $rows = $queryBuilder + ->selectLiteral('mm.uid_local AS product', 'MAX(faq.tstamp) AS tstamp') + ->from('tx_epproducts_product_faq_mm', 'mm') + ->join('mm', 'tx_epproducts_domain_model_faq', 'faq', 'faq.uid = mm.uid_foreign') + ->groupBy('mm.uid_local') + ->execute() + ->fetchAllAssociative() + ; + + return $this->indexTimestamps($rows, 'product'); + } + + /** + * Latest change to a hotel's matchcode aliases, by hotel uid. They are part of the hotel's + * `codes`, which the consumer joins its trip data on. + * + * @return array + */ + public function findMatchcodeTimestamps(): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_matchcode'); + $queryBuilder->getRestrictions()->removeAll(); + + $rows = $queryBuilder + ->selectLiteral('entity AS hotel', 'MAX(tstamp) AS tstamp') + ->from('tx_epproducts_domain_model_matchcode') + ->where($queryBuilder->expr()->eq('entity_type', $queryBuilder->createNamedParameter('hotel'))) + ->groupBy('entity') + ->execute() + ->fetchAllAssociative() + ; + + return $this->indexTimestamps($rows, 'hotel'); + } + + /** + * @return array + */ + private function indexTimestamps(array $rows, string $key): array + { + $timestamps = []; + + foreach ($rows as $row) { + $timestamps[(int) $row[$key]] = (int) $row['tstamp']; + } + + return $timestamps; + } + + private function addTypeConstraints(string $type, QueryBuilder $queryBuilder): void + { + $expr = $queryBuilder->expr(); + + if ('product' === $type) { + // A product that is neither searchable nor bookable is an internal record. + $queryBuilder->andWhere($expr->orX($expr->eq('searchable', 1), $expr->eq('bookable', 1))); + } + + if ('news' === $type) { + // Type 0 is a regular article; 1 and 2 are just links to a page or an external URL. + $queryBuilder->andWhere( + $expr->eq('type', $queryBuilder->createNamedParameter('0')), + $expr->in('fe_group', $queryBuilder->createNamedParameter(['', '0'], Connection::PARAM_STR_ARRAY)) + ); + } + } +} diff --git a/packages/ep_products/Classes/ContentFeed/RecordSerializer.php b/packages/ep_products/Classes/ContentFeed/RecordSerializer.php new file mode 100644 index 0000000..2ed22cc --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/RecordSerializer.php @@ -0,0 +1,513 @@ +recordFinder = $recordFinder; + $this->urlResolver = $urlResolver; + $this->rteLinkResolver = $rteLinkResolver; + $this->connectionPool = $connectionPool; + } + + /** + * @param int[] $uids + * @return array[] one item per eligible uid; ineligible uids are left out + */ + public function serialize(string $type, array $uids): array + { + $rows = $this->recordFinder->find($type, ['*'], $uids); + + if ([] === $rows) { + return []; + } + + switch ($type) { + case 'product': + return $this->serializeProducts($rows); + case 'hotel': + return $this->serializeHotels($rows); + case 'news': + return $this->serializeNews($rows); + default: + return array_values(array_map(function (array $row) use ($type): array { + return $this->serializeDestination($type, $row); + }, $rows)); + } + } + + private function serializeProducts(array $rows): array + { + $uids = array_keys($rows); + $hotels = $this->findMmUids('tx_epproducts_product_hotel_mm', 'uid_local', 'uid_foreign', $uids); + $regions = $this->findMmUids('tx_epproducts_product_region_mm', 'uid_local', 'uid_foreign', $uids); + $faqs = $this->findFaqs($uids); + $faqTimestamps = $this->recordFinder->findFaqTimestamps(); + $infoboxes = $this->findInfoboxTexts(array_column($rows, 'non_bookable_info')); + $items = []; + + foreach ($rows as $uid => $row) { + $season = trim((string) $row['season']); + + $items[] = $this->withLink($row, max((int) $row['tstamp'], $faqTimestamps[$uid] ?? 0)) + [ + 'code' => $this->text($row['code']), + 'busProId' => (int) $row['bus_pro_id'] ?: null, + 'name' => $this->text($row['name']), + 'subline' => $this->text($row['subline']), + 'headline' => $this->text($row['headline']), + 'season' => '' === $season ? 'w' : $season, + 'daytrip' => (bool) $row['daytrip'], + 'bookable' => (bool) $row['bookable'], + 'searchable' => (bool) $row['searchable'], + 'skipassIncluded' => (bool) $row['skipass_included'], + 'conceptUid' => (int) $row['concept'] ?: null, + 'countryUid' => (int) $row['country'] ?: null, + 'regionUid' => (int) $row['region'] ?: null, + 'cityUid' => (int) $row['city'] ?: null, + 'additionalRegionUids' => $regions[$uid] ?? [], + 'hotelUids' => $hotels[$uid] ?? [], + 'keywords' => $this->text($row['keywords']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'teaserLong' => $this->html($row['teaser_long']), + 'feature' => $this->text($row['feature']), + 'conceptDescription' => $this->html($row['concept_description']), + 'boardDescription' => $this->html($row['board_description']), + 'includedServices' => $this->html($row['included_services']), + 'additionalServices' => $this->html($row['additional_services']), + 'programmeDescription' => $this->html($row['programme_description']), + 'skiPassDescription' => $this->html($row['ski_pass_description']), + 'nonBookableInfo' => $infoboxes[(int) $row['non_bookable_info']] ?? null, + ], + 'faqs' => $faqs[$uid] ?? [], + ]; + } + + return $items; + } + + private function serializeHotels(array $rows): array + { + $uids = array_keys($rows); + $aliases = $this->findMatchcodes($uids); + $matchcodeTimestamps = $this->recordFinder->findMatchcodeTimestamps(); + $products = $this->findMmUids('tx_epproducts_product_hotel_mm', 'uid_foreign', 'uid_local', $uids, 'sorting_foreign'); + $eligibleProducts = $this->recordFinder->find('product', ['uid'], array_values(array_unique(array_merge([], ...array_values($products))))); + $items = []; + + foreach ($rows as $uid => $row) { + $code = trim((string) $row['code']); + $codes = array_values(array_unique(array_filter(array_merge([$code], $aliases[$uid] ?? []), 'strlen'))); + + $items[] = $this->withLink($row, max((int) $row['tstamp'], $matchcodeTimestamps[$uid] ?? 0)) + [ + 'code' => '' === $code ? null : $code, + 'codes' => $codes, + 'name' => $this->text($row['name']), + 'shortName' => $this->text($row['short_name']), + 'headline' => $this->text($row['headline']), + 'type' => (int) $row['type'], + 'category' => (int) $row['category'], + 'new' => (bool) $row['new'], + 'earlybird' => (bool) $row['earlybird'], + 'countryUid' => (int) $row['country'] ?: null, + 'regionUid' => (int) $row['region'] ?: null, + 'cityUid' => (int) $row['city'] ?: null, + 'productUids' => array_values(array_filter($products[$uid] ?? [], function (int $productUid) use ($eligibleProducts): bool { + return isset($eligibleProducts[$productUid]); + })), + 'address' => $this->text($row['address']), + 'latitude' => $this->text($row['latitude']), + 'longitude' => $this->text($row['longitude']), + 'facts' => [ + 'bedsCount' => (int) $row['beds_count'] ?: null, + 'parkingCount' => (int) $row['parking_count'] ?: null, + 'altitude' => $this->text($row['altitude']), + 'distanceBus' => $this->text($row['distance_bus']), + 'distanceLift' => $this->text($row['distance_lift']), + 'shower' => $this->text($row['shower']), + 'partyroom' => (bool) $row['partyroom'], + 'gamesroom' => (bool) $row['gamesroom'], + 'sauna' => (bool) $row['sauna'], + 'wifi' => (bool) $row['wifi'], + ], + 'icons' => HotelIconUtility::getIcons($row), + 'keywords' => $this->text($row['keywords']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'features' => $this->html($row['features']), + 'roomTypes' => $this->html($row['room_types']), + 'additionalInformation' => $this->html($row['additional_information']), + ], + ]; + } + + return $items; + } + + private function serializeDestination(string $type, array $row): array + { + $item = $this->withLink($row, (int) $row['tstamp']) + [ + 'name' => $this->text($row['name']), + 'headline' => $this->text($row['headline']), + ]; + + switch ($type) { + case 'region': + return $item + [ + 'season' => $this->text($row['season']), + 'keywords' => $this->text($row['keywords']), + 'countryUid' => (int) $row['country'] ?: null, + 'facts' => [ + 'length' => $this->text($row['length']), + 'height' => $this->text($row['height']), + 'lifts' => $this->text($row['lifts']), + ], + 'webcam' => $this->findWebcamUrl((int) $row['webcam']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'feature' => $this->text($row['feature']), + 'skiArea' => $this->html($row['ski_area']), + 'skiAreaExtended' => $this->html($row['ski_area_extended']), + 'skiPass' => $this->html($row['ski_pass']), + 'news' => $this->html($row['news']), + ], + ]; + case 'country': + return $item + [ + 'code' => $this->text($row['code']), + 'season' => $this->text($row['season']), + 'keywords' => $this->text($row['keywords']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'descriptionExtended' => $this->html($row['description_extended']), + ], + ]; + case 'city': + return $item + [ + 'season' => $this->text($row['season']), + 'keywords' => $this->text($row['keywords']), + 'countryUid' => (int) $row['country'] ?: null, + 'regionUid' => (int) $row['region'] ?: null, + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'descriptionExtended' => $this->html($row['description_extended']), + 'party' => $this->html($row['party']), + 'leisure' => $this->html($row['leisure']), + ], + ]; + default: // concept + return $item + [ + 'code' => $this->text($row['code']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'descriptionExtended' => $this->html($row['description_extended']), + 'popupText' => $this->html($row['popup_text']), + ], + ]; + } + } + + private function serializeNews(array $rows): array + { + $uids = array_keys($rows); + $tags = $this->findNewsTags($uids); + $categories = $this->findNewsCategories($uids); + $items = []; + + foreach ($rows as $uid => $row) { + $link = $this->urlResolver->getNewsLink($uid, (int) $row['pid']); + + if (null === $link) { + continue; + } + + $items[] = [ + 'uid' => $uid, + 'url' => $link['url'], + 'site' => $link['site'], + 'modified' => date('c', (int) $row['tstamp']), + 'title' => $this->text($row['title']), + 'teaser' => $this->html($row['teaser']), + 'bodytext' => $this->html($row['bodytext']), + 'datetime' => (int) $row['datetime'] > 0 ? date('c', (int) $row['datetime']) : null, + 'tags' => $tags[$uid] ?? [], + 'categories' => $categories[$uid] ?? [], + ]; + } + + return $items; + } + + /** + * The identity and link fields every record carries, with the same values as the manifest. + */ + private function withLink(array $row, int $modified): array + { + $link = $this->urlResolver->getRecordLink((string) $row['detail_page']); + + return [ + 'uid' => (int) $row['uid'], + 'url' => $link['url'], + 'site' => $link['site'], + 'modified' => date('c', $modified), + ]; + } + + /** + * @param int[] $uids + * @return array related uids by $keyField, in $sortField order + */ + private function findMmUids(string $table, string $keyField, string $valueField, array $uids, string $sortField = 'sorting'): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable($table); + $rows = $queryBuilder + ->select($keyField, $valueField) + ->from($table) + ->where($queryBuilder->expr()->in($keyField, $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY))) + ->orderBy($keyField) + ->addOrderBy($sortField) + ->execute() + ->fetchAllAssociative() + ; + + $related = []; + + foreach ($rows as $row) { + $related[(int) $row[$keyField]][] = (int) $row[$valueField]; + } + + return $related; + } + + /** + * @param int[] $productUids + * @return array FAQs by product uid, in the order the editor arranged them + */ + private function findFaqs(array $productUids): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_faq'); + $rows = $queryBuilder + ->select('mm.uid_local AS product', 'faq.question', 'faq.answer', 'faq.category') + ->from('tx_epproducts_domain_model_faq', 'faq') + ->join('faq', 'tx_epproducts_product_faq_mm', 'mm', 'mm.uid_foreign = faq.uid') + ->where($queryBuilder->expr()->in('mm.uid_local', $queryBuilder->createNamedParameter($productUids, Connection::PARAM_INT_ARRAY))) + ->orderBy('mm.uid_local') + ->addOrderBy('mm.sorting') + ->execute() + ->fetchAllAssociative() + ; + + $faqs = []; + + foreach ($rows as $row) { + $faqs[(int) $row['product']][] = [ + 'question' => $this->text($row['question']), + 'answer' => $this->html($row['answer']), + 'category' => $this->text($row['category']), + ]; + } + + return $faqs; + } + + /** + * Matchcode aliases by hotel uid. With the hotel's own code they are every code BusPro may use + * for it - the same set DateImportService::getHotelMappings() resolves. + * + * @param int[] $hotelUids + * @return array + */ + private function findMatchcodes(array $hotelUids): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_matchcode'); + $rows = $queryBuilder + ->select('entity', 'code') + ->from('tx_epproducts_domain_model_matchcode') + ->where( + $queryBuilder->expr()->eq('entity_type', $queryBuilder->createNamedParameter('hotel')), + $queryBuilder->expr()->in('entity', $queryBuilder->createNamedParameter($hotelUids, Connection::PARAM_INT_ARRAY)) + ) + ->orderBy('code') + ->execute() + ->fetchAllAssociative() + ; + + $codes = []; + + foreach ($rows as $row) { + $codes[(int) $row['entity']][] = trim((string) $row['code']); + } + + return $codes; + } + + /** + * @param array $infoboxUids + * @return array + */ + private function findInfoboxTexts(array $infoboxUids): array + { + $infoboxUids = array_values(array_filter(array_map('intval', $infoboxUids))); + + if ([] === $infoboxUids) { + return []; + } + + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_infobox'); + $rows = $queryBuilder + ->select('uid', 'text') + ->from('tx_epproducts_domain_model_infobox') + ->where($queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter(array_unique($infoboxUids), Connection::PARAM_INT_ARRAY))) + ->execute() + ->fetchAllAssociative() + ; + + $texts = []; + + foreach ($rows as $row) { + $texts[(int) $row['uid']] = $this->html($row['text']); + } + + return $texts; + } + + private function findWebcamUrl(int $webcamUid): ?string + { + if (0 === $webcamUid) { + return null; + } + + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_webcam'); + $url = $queryBuilder + ->select('url') + ->from('tx_epproducts_domain_model_webcam') + ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($webcamUid, \PDO::PARAM_INT))) + ->execute() + ->fetchOne() + ; + + return false === $url ? null : $this->text($url); + } + + /** + * @param int[] $newsUids + * @return array + */ + private function findNewsTags(array $newsUids): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_news_domain_model_tag'); + $rows = $queryBuilder + ->select('mm.uid_local AS news', 'tag.title') + ->from('tx_news_domain_model_tag', 'tag') + ->join('tag', 'tx_news_domain_model_news_tag_mm', 'mm', 'mm.uid_foreign = tag.uid') + ->where($queryBuilder->expr()->in('mm.uid_local', $queryBuilder->createNamedParameter($newsUids, Connection::PARAM_INT_ARRAY))) + ->orderBy('mm.sorting') + ->execute() + ->fetchAllAssociative() + ; + + $tags = []; + + foreach ($rows as $row) { + $tags[(int) $row['news']][] = trim((string) $row['title']); + } + + return $tags; + } + + /** + * @param int[] $newsUids + * @return array + */ + private function findNewsCategories(array $newsUids): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_category'); + $rows = $queryBuilder + ->select('mm.uid_foreign AS news', 'category.title') + ->from('sys_category', 'category') + ->join('category', 'sys_category_record_mm', 'mm', 'mm.uid_local = category.uid') + ->where( + $queryBuilder->expr()->eq('mm.tablenames', $queryBuilder->createNamedParameter('tx_news_domain_model_news')), + $queryBuilder->expr()->eq('mm.fieldname', $queryBuilder->createNamedParameter('categories')), + $queryBuilder->expr()->in('mm.uid_foreign', $queryBuilder->createNamedParameter($newsUids, Connection::PARAM_INT_ARRAY)) + ) + ->orderBy('mm.sorting_foreign') + ->execute() + ->fetchAllAssociative() + ; + + $categories = []; + + foreach ($rows as $row) { + $categories[(int) $row['news']][] = trim((string) $row['title']); + } + + return $categories; + } + + /** + * Rich text as stored, with internal links made absolute. + */ + private function html($value): ?string + { + $value = trim((string) $value); + + return '' === $value ? null : $this->rteLinkResolver->resolve($value); + } + + /** + * Plain text with whitespace collapsed. Product names carry literal line breaks the website + * splits on for layout ("Apartments \nVal Thorens"); the chatbot needs them as one line. + */ + private function text($value): ?string + { + $value = trim((string) preg_replace('/\s+/u', ' ', (string) $value)); + + return '' === $value ? null : $value; + } +} diff --git a/packages/ep_products/Classes/ContentFeed/RteLinkResolver.php b/packages/ep_products/Classes/ContentFeed/RteLinkResolver.php new file mode 100644 index 0000000..83dafd8 --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/RteLinkResolver.php @@ -0,0 +1,104 @@ +linkService = $linkService; + $this->urlResolver = $urlResolver; + } + + public function resolve(string $html): string + { + if (false === strpos($html, 't3://')) { + return $html; + } + + return (string) preg_replace_callback( + '/\shref=(["\'])(t3:\/\/[^"\']*)\1/i', + function (array $matches): string { + $url = $this->resolveLink(html_entity_decode($matches[2], ENT_QUOTES | ENT_HTML5)); + + return null === $url ? '' : ' href="' . htmlspecialchars($url) . '"'; + }, + $html + ); + } + + private function resolveLink(string $link): ?string + { + try { + $data = $this->linkService->resolve($link); + } catch (\Throwable $e) { + return null; + } + + if (LinkService::TYPE_PAGE === ($data['type'] ?? null)) { + $pageUid = $this->urlResolver->parsePageReference('t3://page?uid=' . ($data['pageuid'] ?? '')); + $target = null === $pageUid ? null : $this->urlResolver->resolveLinkTarget($pageUid); + $url = null === $target ? null : $this->urlResolver->getPageUrl($target); + + if (null !== $url && '' !== (string) ($data['fragment'] ?? '')) { + $url .= '#' . $data['fragment']; + } + + return $url; + } + + if (LinkService::TYPE_FILE === ($data['type'] ?? null) && ($data['file'] ?? null) instanceof File) { + return $this->absolute((string) $data['file']->getPublicUrl()); + } + + if (LinkService::TYPE_URL === ($data['type'] ?? null)) { + return (string) $data['url']; + } + + return null; + } + + /** + * File URLs come back relative to the document root; the consumer has no page to resolve + * them against for record text, so make them absolute against the main site. + */ + private function absolute(string $url): ?string + { + if ('' === $url) { + return null; + } + + if (1 === preg_match('/^https?:\/\//', $url)) { + return $url; + } + + $sites = $this->urlResolver->getIncludedSites(); + + if ([] === $sites) { + return null; + } + + return rtrim((string) $sites[0]->getBase(), '/') . '/' . ltrim($url, '/'); + } +} diff --git a/packages/ep_products/Classes/ContentFeed/UrlResolver.php b/packages/ep_products/Classes/ContentFeed/UrlResolver.php new file mode 100644 index 0000000..a52c644 --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/UrlResolver.php @@ -0,0 +1,214 @@ + news detail page uid by site identifier + */ + private $newsDetailPages; + + /** + * @var array + */ + private $sitesByPage = []; + + public function __construct(SiteFinder $siteFinder, PageTree $pageTree, ContentFeedConfiguration $configuration) + { + $this->siteFinder = $siteFinder; + $this->pageTree = $pageTree; + $this->includedSites = $configuration->getSites(); + $this->newsDetailPages = $configuration->getNewsDetailPages(); + } + + /** + * @return Site[] included sites, in configuration order + */ + public function getIncludedSites(): array + { + $sites = []; + + foreach ($this->includedSites as $identifier) { + try { + $sites[] = $this->siteFinder->getSiteByIdentifier($identifier); + } catch (SiteNotFoundException $e) { + // A misspelt identifier must not take the whole feed down; it just contributes nothing. + } + } + + return $sites; + } + + /** + * The page's site, or null when it belongs to no site or to one outside the feed. + */ + public function getIncludedSite(int $pageUid): ?Site + { + if (!array_key_exists($pageUid, $this->sitesByPage)) { + try { + $site = $this->siteFinder->getSiteByPageId($pageUid); + $this->sitesByPage[$pageUid] = in_array($site->getIdentifier(), $this->includedSites, true) ? $site : null; + } catch (SiteNotFoundException $e) { + $this->sitesByPage[$pageUid] = null; + } + } + + return $this->sitesByPage[$pageUid]; + } + + public function getPageUrl(int $pageUid, int $languageId = 0, array $parameters = []): ?string + { + $site = $this->getIncludedSite($pageUid); + + if (null === $site) { + return null; + } + + try { + $language = $site->getLanguageById($languageId); + + return (string) $site->getRouter()->generateUri($pageUid, ['_language' => $language] + $parameters); + } catch (\Throwable $e) { + // Unroutable pages (missing slug, disabled language) are left out rather than failing the feed. + return null; + } + } + + /** + * Parses a typolink page reference as stored in `detail_page`: a plain uid or `t3://page?uid=N`. + */ + public function parsePageReference(string $reference): ?int + { + $reference = trim($reference); + + if (MathUtility::canBeInterpretedAsInteger($reference)) { + return (int) $reference > 0 ? (int) $reference : null; + } + + if (1 === preg_match('/^t3:\/\/page\?(?:.*&)?uid=(\d+)/', html_entity_decode($reference), $matches)) { + return (int) $matches[1]; + } + + return null; + } + + /** + * Follows shortcut pages the way a typolink does, and returns the uid of the visible standard + * page a link to $pageUid ends up on - or null when the chain breaks or loops. + */ + public function resolveLinkTarget(int $pageUid): ?int + { + for ($depth = 0; $depth < self::MAX_SHORTCUT_DEPTH; $depth++) { + $page = $this->pageTree->getPage($pageUid); + + if (null === $page || !$this->pageTree->isVisible($page)) { + return null; + } + + if ((int) $page['doktype'] !== self::DOKTYPE_SHORTCUT) { + return (int) $page['doktype'] === self::DOKTYPE_DEFAULT ? $pageUid : null; + } + + $next = $this->getShortcutTarget($page); + + if (null === $next) { + return null; + } + + $pageUid = $next; + } + + return null; + } + + /** + * @return array{url: ?string, site: ?string} + */ + public function getRecordLink(string $detailPage): array + { + $pageUid = $this->parsePageReference($detailPage); + $target = null === $pageUid ? null : $this->resolveLinkTarget($pageUid); + $site = null === $target ? null : $this->getIncludedSite($target); + $url = null === $site ? null : $this->getPageUrl($target); + + return [ + 'url' => $url, + 'site' => null === $url ? null : $site->getIdentifier(), + ]; + } + + /** + * News has no detail_page of its own: the storage folder decides the site, and the site's + * configured detail page carries the article through the NewsDetailPlugin route enhancer. + * + * @return array{url: ?string, site: string}|null null when the article is stored outside the feed's sites + */ + public function getNewsLink(int $newsUid, int $storagePid): ?array + { + $site = $this->getIncludedSite($storagePid); + + if (null === $site) { + return null; + } + + $detailPage = $this->newsDetailPages[$site->getIdentifier()] ?? null; + $url = null === $detailPage ? null : $this->getPageUrl($detailPage, 0, [ + 'tx_news_pi1' => ['action' => 'detail', 'controller' => 'News', 'news' => $newsUid], + ]); + + return ['url' => $url, 'site' => $site->getIdentifier()]; + } + + private function getShortcutTarget(array $page): ?int + { + switch ((int) $page['shortcut_mode']) { + case self::SHORTCUT_MODE_NONE: + return (int) $page['shortcut'] > 0 ? (int) $page['shortcut'] : null; + case self::SHORTCUT_MODE_FIRST_SUBPAGE: + foreach ($this->pageTree->getChildren((int) $page['uid']) as $childUid) { + if ($this->pageTree->isVisible($this->pageTree->getPage($childUid))) { + return $childUid; + } + } + + return null; + case self::SHORTCUT_MODE_PARENT_PAGE: + return (int) $page['pid'] > 0 ? (int) $page['pid'] : null; + default: + // Random subpage: there is no stable URL to give. + return null; + } + } +} diff --git a/packages/ep_products/Classes/Controller/AjaxCalendarController.php b/packages/ep_products/Classes/Controller/AjaxCalendarController.php new file mode 100644 index 0000000..259b283 --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxCalendarController.php @@ -0,0 +1,391 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\BpnConnect\ApiClient as BpnConnectClient; +use EP\EpProducts\MyEP\ApiClient as MyEpClient; +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\MyEP\ApiException; +use EP\EpProducts\Utility\DateUtility; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; + +class AjaxCalendarController extends ActionController +{ + private const CACHE_TTL = 3600; + + protected BpnConnectClient $bpnConnectClient; + protected MyEpClient $myEpClient; + protected FrontendInterface $cache; + + public function __construct(BpnConnectClient $bpnConnectClient, MyEpClient $myEpClient, FrontendInterface $cache) + { + $this->bpnConnectClient = $bpnConnectClient; + $this->myEpClient = $myEpClient; + $this->cache = $cache; + } + + public function priceCalendarAction(Hotel $hotel, int $months = 12): void + { + $hotelCode = trim($hotel->getCode() ?? ''); + $this->tagPageCacheForHotel($hotelCode); + + $dateRange = DateUtility::getDateRange(null, null, $months); + $period = $dateRange['period']; + $today = new \DateTimeImmutable('today'); + + $priceDataByDate = []; + $error = false; + $errorMessage = ''; + + if ($hotelCode !== '') { + try { + // This action is cacheable, so the rendered markup is held in the page cache + // for config.cache_period. An additional application-level cache here would + // key on exactly the same granularity (hotel + date range) and only stack a + // second TTL on top, pushing worst-case staleness past the freshness budget. + $rawData = $this->myEpClient->getPriceConfigData($hotelCode); + + $availableDates = []; + foreach ($rawData as $item) { + if (($item['status'] ?? '') === 'OK') { + $availableDates[$item['date']] = true; + } + } + + // The API no longer honours a date-range constraint, so it may return data + // further out than the default $months grid covers. Grow the grid to match + // whatever the API actually returned rather than silently dropping the tail. + $lastDataDate = null; + + foreach ($rawData as $item) { + $date = $item['date']; + $dt = \DateTimeImmutable::createFromFormat('Y-m-d', $date); + if ($dt < $today) { + continue; + } + + if (null === $lastDataDate || $dt > $lastDataDate) { + $lastDataDate = $dt; + } + + $prevDate = $dt->modify('-1 day')->format('Y-m-d'); + $isAvailable = ($item['status'] ?? '') === 'OK'; + $prevAvailable = isset($availableDates[$prevDate]); + + if ($isAvailable) { + $dayStatus = $prevAvailable ? 'ok' : 'blocked-to-ok'; + } elseif ($prevAvailable) { + $dayStatus = 'checkout-only'; + } else { + $dayStatus = 'blocked'; + } + + $priceDataByDate[$date] = [ + 'status' => $dayStatus, + 'available' => $isAvailable ? 1 : 0, + 'tooltip' => $this->buildTooltip($dayStatus, $item), + 'pricePerNight' => $item['pricePerNight'] ?? null, + 'defaultPricePerNight' => $item['defaultPricePerNight'] ?? null, + 'type' => $item['type'] ?? null, + 'currency' => $item['currency'] ?? null, + 'includedPax' => $item['includedPax'] ?? null, + 'minNights' => (int)($item['minNights'] ?? 0), + ]; + } + + if (null !== $lastDataDate) { + $dataEnd = $lastDataDate->modify('first day of next month')->setTime(0, 0, 0); + if ($dataEnd > $period->getEndDate()) { + $period = new \League\Period\Period($period->getStartDate(), $dataEnd); + } + } + } catch (ApiException $e) { + $error = true; + $errorMessage = $e->getMessage(); + $this->disablePageCache(); + } + } else { + $error = true; + $errorMessage = 'Missing hotel code'; + $this->disablePageCache(); + } + + $this->view->assignMultiple([ + 'priceDataByDate' => $priceDataByDate, + 'calendarMonths' => $this->buildCalendarMonths($period), + 'error' => $error, + 'errorMessage' => $errorMessage, + ]); + } + + public function priceConfigAction(Hotel $hotel): void + { + $hotelCode = trim($hotel->getCode() ?? ''); + $this->tagPageCacheForHotel($hotelCode); + + $priceConfig = [ + 'data' => [], + 'success' => true, + 'error' => null, + ]; + + try { + $priceConfig['data'] = $this->myEpClient->getPriceConfigData($hotelCode); + } catch (ApiException $e) { + $priceConfig['success'] = false; + $priceConfig['error'] = $e->getMessage(); + $this->disablePageCache(); + } + + $this->view->assign('priceConfig', $priceConfig); + } + + public function priceTableAction(Hotel $hotel, string $year = null): void + { + $year = $year ?? (new \DateTimeImmutable())->format('Y'); + $hotelCode = trim($hotel->getCode() ?? ''); + $this->tagPageCacheForHotel($hotelCode); + + $priceTable = [ + 'data' => [], + 'success' => true, + 'error' => null, + ]; + + try { + $priceTable['data'] = $this->myEpClient->getPriceTableData($hotelCode, $year); + } catch (ApiException $e) { + $priceTable['success'] = false; + $priceTable['error'] = $e->getMessage(); + $this->disablePageCache(); + } + + $this->view->assignMultiple([ + 'hotel' => $hotel, + 'year' => $year, + 'priceTable' => $priceTable, + ]); + } + + /** + * @param Hotel $hotel + * @param string $year + * @param string $month + * @param int $months + */ + public function rangeAction(Hotel $hotel, $year = null, $month = null, $months = 1) + { + $dateRange = DateUtility::getDateRange($year, $month, $months); + $period = $dateRange['period']; + $apiDateFrom = $dateRange['dateFrom']; + $apiDateTo = $dateRange['dateTo']; + $hotelCode = trim((string) $hotel->getCode()); + + $calendar = [ + 'meta' => [], + 'data' => [], + ]; + + if ($hotelCode === '') { + $calendar['error'] = true; + } else { + try { + $cacheIdentifier = $this->getCacheIdentifier( + $hotelCode, + $apiDateFrom, + $apiDateTo, + BpnConnectClient::CALENDAR_MODE_DAYS + ); + $cachedCalendar = $this->cache->get($cacheIdentifier); + + if (false !== $cachedCalendar) { + $calendar = $cachedCalendar; + } else { + $calendar = $this->bpnConnectClient->getCalendar( + $hotelCode, + new \DateTimeImmutable($apiDateFrom), + new \DateTimeImmutable($apiDateTo), + BpnConnectClient::CALENDAR_MODE_DAYS + ); + $this->cache->set($cacheIdentifier, $calendar, [], self::CACHE_TTL); + } + } catch (\Throwable $e) { + $calendar['error'] = true; + } + } + + $this->view->assignMultiple([ + 'months' => $months, + 'hotel' => $hotel, + 'calendar' => $calendar, + 'calendarMonths' => $this->buildCalendarMonths($period), + ]); + } + + /** + * Tag this page-cache entry with the hotel it renders, so the entry can be flushed for + * that hotel alone rather than by clearing the whole page cache and re-warming every + * calendar. Tags are stored alongside the entry by + * TypoScriptFrontendController::setPageCacheContent(). + * + * Used manually today, when one hotel's availability needs re-fetching before its hour + * is up: + * + * typo3cms cache:flushtags ep_hotel_ --groups pages + */ + private function tagPageCacheForHotel(string $hotelCode): void + { + if ('' === $hotelCode) { + return; + } + + if (isset($GLOBALS['TSFE']) && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController) { + $GLOBALS['TSFE']->addCacheTags(['ep_hotel_' . $hotelCode]); + } + } + + /** + * The calendar actions are cacheable, so an API failure would otherwise pin the error + * markup in the page cache for the full cache_period. Drop caching for this request so + * the next visitor gets a fresh attempt instead of an hour-old error message. + * + * The second argument must be true: set_no_cache() ignores the call otherwise, because + * $TYPO3_CONF_VARS['FE']['disableNoCacheParameter'] defaults to true. + */ + private function disablePageCache(): void + { + if (isset($GLOBALS['TSFE']) && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController) { + $GLOBALS['TSFE']->set_no_cache('Price calendar data could not be retrieved', true); + } + } + + private function buildTooltip(string $status, array $item): string + { + if ($status === 'blocked') { + return 'Nicht verfügbar'; + } + if ($status === 'checkout-only') { + return 'Nur Abreise'; + } + return $this->buildPriceTooltip($item); + } + + private function buildPriceTooltip(array $item): string + { + $price = $item['pricePerNight'] ?? null; + $currency = $this->getCurrencySymbol($item['currency'] ?? null); + + if ($price !== null) { + $priceStr = (($item['type'] ?? null) === 'discount') + ? $price . ' ' . $currency + : 'ab ' . $price . ' ' . $currency; + } else { + $priceStr = 'Anfrage'; + } + + $minNights = (int)($item['minNights'] ?? 0); + + return $minNights > 1 + ? $priceStr . ' · min. ' . $minNights . ' Nächte' + : $priceStr; + } + + private function getCurrencySymbol(?string $currency): string + { + if (null === $currency || '' === $currency) { + return '€'; + } + + return 'EUR' === $currency ? '€' : $currency; + } + + /** + * @return array> + */ + private function buildCalendarMonths(\League\Period\Period $period): array + { + $months = []; + $current = new \DateTimeImmutable($period->getStartDate()->format('Y-m-01 00:00:00')); + $lastDay = $period->getEndDate()->sub(new \DateInterval('P1D')); + $lastMonthStart = new \DateTimeImmutable($lastDay->format('Y-m-01 00:00:00')); + + while ($current <= $lastMonthStart) { + $months[] = $this->buildCalendarMonth($current); + $current = $current->modify('first day of next month'); + } + + return $months; + } + + /** + * @return array + */ + private function buildCalendarMonth(\DateTimeImmutable $monthStart): array + { + $firstOfMonth = $monthStart->setTime(0, 0, 0); + $lastOfMonth = $firstOfMonth->modify('last day of this month'); + $gridStart = $firstOfMonth->modify('monday this week'); + $gridEnd = $lastOfMonth->modify('sunday this week'); + + $weeks = []; + $week = []; + $current = $gridStart; + + while ($current <= $gridEnd) { + $week[] = [ + 'date' => $current, + 'inMonth' => $current->format('Y-m') === $firstOfMonth->format('Y-m'), + ]; + + if (count($week) === 7) { + $weeks[] = $week; + $week = []; + } + + $current = $current->add(new \DateInterval('P1D')); + } + + return [ + 'date' => $firstOfMonth, + 'weeks' => $weeks, + ]; + } + + private function getCacheIdentifier(string $hotelCode, string $dateFrom, string $dateTo, string $mode): string + { + return 'bpn_connect_calendar_' . sha1(implode('|', [ + $hotelCode, + $dateFrom, + $dateTo, + $mode, + ])); + } + +} diff --git a/packages/ep_products/Classes/Controller/AjaxContingentController.php b/packages/ep_products/Classes/Controller/AjaxContingentController.php new file mode 100644 index 0000000..265ed1d --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxContingentController.php @@ -0,0 +1,102 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\ContingentRepository; +use EP\EpProducts\Service\ContingentDataService; +use EP\EpProducts\Service\MyEpUrlResolverService; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class AjaxContingentController extends ActionController +{ + /** + * @var \EP\EpProducts\Domain\Repository\ContingentRepository + */ + protected $contingentRepository; + /** + * @var ContingentDataService + */ + protected $contingentDataService; + + /** + * @var MyEpUrlResolverService + */ + protected $myEpUrlResolverService; + + /** + * @param ContingentRepository $contingentRepository + * @param ContingentDataService $contingentDataService + * @param MyEpUrlResolverService $myEpUrlResolverService + */ + public function __construct( + ContingentRepository $contingentRepository, + ContingentDataService $contingentDataService, + MyEpUrlResolverService $myEpUrlResolverService + ) { + $this->contingentRepository = $contingentRepository; + $this->contingentDataService = $contingentDataService; + $this->myEpUrlResolverService = $myEpUrlResolverService; + } + + public function initializeAction() + { + if ($this->request->hasArgument('dateFrom')) { + $dateFrom = new \DateTime($this->request->getArgument('dateFrom')); + $this->request->setArgument('dateFrom', $dateFrom); + } + if ($this->request->hasArgument('dateTo')) { + $dateTo = new \DateTime($this->request->getArgument('dateTo')); + $this->request->setArgument('dateTo', $dateTo); + } + } + + /** + * @param Hotel $hotel + * @param Product $product + * @param \DateTime $dateFrom + * @param \DateTime $dateTo + * @return string + * @throws \Doctrine\DBAL\DBALException + */ + public function roomsAction(Hotel $hotel, Product $product, \DateTime $dateFrom, \DateTime $dateTo) + { + $myEpUrl = $this->myEpUrlResolverService->resolve(); + $rooms = $this->contingentDataService->getAvailableRooms( + $dateFrom->format('Y-m-d'), + $dateTo->format('Y-m-d'), + $hotel, + $product, + $myEpUrl + ); + + return \json_encode($rooms); + } + +} diff --git a/packages/ep_products/Classes/Controller/AjaxDateController.php b/packages/ep_products/Classes/Controller/AjaxDateController.php new file mode 100644 index 0000000..54f215a --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxDateController.php @@ -0,0 +1,148 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Service\DateService; +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\ResellerDataService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +class AjaxDateController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var DateService + */ + protected $dateService; + + /** + * @var ResellerDataService + */ + protected $resellerDataService; + + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @param FilterService $filterService + * @param DateService $dateService + * @param ResellerDataService $resellerDataService + * @param ProductRepository $productRepository + */ + public function __construct + ( + FilterService $filterService, + DateService $dateService, + ResellerDataService $resellerDataService, + ProductRepository $productRepository + ) + { + $this->filterService = $filterService; + $this->dateService = $dateService; + $this->resellerDataService = $resellerDataService; + $this->productRepository = $productRepository; + } + + public function initializeDatesAction() + { + if ($this->request->hasArgument('dateFrom')) { + $dateFrom = $this->request->getArgument('dateFrom'); + $this->request->setArgument('dateFrom', $dateFrom === '' ? null : $dateFrom); + } + if ($this->request->hasArgument('dateTo')) { + $dateTo = $this->request->getArgument('dateTo'); + $this->request->setArgument('dateTo', $dateTo === '' ? null : $dateTo); + } + } + + /** + * @param Product $product + * @param Hotel $hotel + * @param string $paCode + * @param string $dateFrom + * @param string $dateTo + * + * @return string + * @throws \Doctrine\DBAL\DBALException + */ + public function datesAction(Product $product, Hotel $hotel = null, $paCode = null, $dateFrom = null, $dateTo = null) + { + $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); + $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids); + $filterSettings['dateFrom'] = $dateFrom; + $filterSettings['dateTo'] = $dateTo; + + $altProductLink = null; + $altLabel = null; + if ($product->getAltProduct()) { + $altProduct = $product->getAltProduct(); + if ($this->productRepository->hasDates($altProduct)) { + /** @var ContentObjectRenderer $contentObject */ + $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class); + $altProductLink = $contentObject->typoLink_URL( + [ + 'parameter' => $altProduct->getDetailPage(), + ] + ); + $altLabel = $product->getAltLabel(); + } + } + $datesTable = $this->dateService->getDatesTable([ + 'product' => $product, + 'hotel' => $hotel, + 'filterSettings' => $filterSettings, + ]); + $hasSurchargeOrDiscount = $this->dateService->checkSurcharcheOrDiscount($datesTable); + + return json_encode([ + 'dates' => $datesTable, + 'bookable' => $product->getBookable(), + 'hotelName' => $hotel->getName(), + 'altProductLink' => $altProductLink, + 'altLabel' => $altLabel, + 'hasSurcharge' => $hasSurchargeOrDiscount['surcharge'], + 'hasDiscount' => $hasSurchargeOrDiscount['discount'], + ], JSON_THROW_ON_ERROR); + } + +} diff --git a/packages/ep_products/Classes/Controller/AjaxFilterPanelController.php b/packages/ep_products/Classes/Controller/AjaxFilterPanelController.php new file mode 100644 index 0000000..840b8bb --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxFilterPanelController.php @@ -0,0 +1,100 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\SearchResultService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class AjaxFilterPanelController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var SearchResultService + */ + private $searchResultService; + + /** + * @param FilterService $filterService + */ + public function __construct(FilterService $filterService, SearchResultService $searchResultService) + { + $this->filterService = $filterService; + $this->searchResultService = $searchResultService; + } + + public function initializeAction() + { + $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); + $this->processFilterSettingsArgument($forcedConceptUids); + } + + /** + * @param array $filterSettings + */ + public function indexAction(array $filterSettings) + { + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']); + + $searchResult = $this + ->searchResultService + ->getResult($filterSettings, $excludedConceptUids); + + $filterOptions = $this->filterService->collectFilterOptionsFromSearchResult($searchResult); + + $resultsUri = $this->uriBuilder + ->reset() + ->setCreateAbsoluteUri(true) + ->setTargetPageUid((int) $this->settings['defaultSearchPageUid']) + ->uriFor( + 'searchresult', + ['filterSettings' => $filterSettings], + 'Search', + 'epproducts', + 'searchresult' + ); + + $filterOptions->setResultsUri($resultsUri); + + $this->view->assignMultiple([ + 'filterSettings' => $filterSettings, + 'filterOptions' => $filterOptions, + 'stimulusController' => 'mobile-search', + ]); + } + +} diff --git a/packages/ep_products/Classes/Controller/AjaxSearchController.php b/packages/ep_products/Classes/Controller/AjaxSearchController.php new file mode 100644 index 0000000..9a2ec05 --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxSearchController.php @@ -0,0 +1,132 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\SearchResultService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use EP\EpProducts\Utility\DateUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class AjaxSearchController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var SearchResultService + */ + protected $searchResultService; + + /** + * @param FilterService $filterService + * @param SearchResultService $searchResultService + */ + public function __construct(FilterService $filterService, SearchResultService $searchResultService) + { + $this->filterService = $filterService; + $this->searchResultService = $searchResultService; + } + + public function initializeSearchresultAction() + { + $forcedConceptUids = GeneralUtility::intExplode(',', $this->settings['forcedConceptUids'], true); + $this->processFilterSettingsArgument($forcedConceptUids); + } + + /** + * @param array $filterSettings + */ + public function searchresultAction(array $filterSettings): void + { + $dateSelected = false; + $organic = true; + $forcedConceptUids = GeneralUtility::intExplode(',', $this->settings['forcedConceptUids'], true); + $excludedConceptUids = GeneralUtility::intExplode(',', $this->settings['excludedConceptUids'], true); + + if (isset($filterSettings['dateFrom']) || isset($filterSettings['dateTo'])) { + $dateSelected = true; + } + + $searchResult = $this + ->searchResultService + ->getResult($filterSettings, $excludedConceptUids); + + // No results? First retry without date constraint if any + if ((!empty($filterSettings['dateFrom']) || !empty($filterSettings['dateTo'])) && 0 === $searchResult->getTotal()) { + $filterSettings['dateFrom'] = null; + $filterSettings['dateTo'] = null; + $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); + $organic = false; + } + + // Still no results? Remove all constraints and search again with defaults + if (0 === $searchResult->getTotal()) { + $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids); + $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); + $organic = false; + } + + $filterOptions = $this->filterService->collectFilterOptionsFromSearchResult($searchResult); + + $referringPageUri = $this + ->uriBuilder + ->reset() + ->setTargetPageUid((int)$this->settings['defaultSearchPageUid']) + ->setCreateAbsoluteUri(true) + ->uriFor( + 'searchresult', + ['filterSettings' => $filterSettings], + 'Search', + 'epproducts', + 'searchresult' + ) + ; + + $this->view->assignMultiple([ + 'filterSettings' => $filterSettings, + 'filterOptions' => $filterOptions, + 'searchResult' => $searchResult->getData(), + 'referringPageUri' => $referringPageUri, + 'stimulusController' => 'search', + 'meta' => [ + 'organic' => $organic, + 'total' => $searchResult->getTotal(), + 'dateRange' => DateUtility::formatDateRange($filterSettings['dateFrom'], $filterSettings['dateTo']), + 'dateSelected' => $dateSelected, + ], + ]); + } + +} diff --git a/packages/ep_products/Classes/Controller/AjaxTableController.php b/packages/ep_products/Classes/Controller/AjaxTableController.php new file mode 100644 index 0000000..8b3cdc1 --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxTableController.php @@ -0,0 +1,386 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Date; +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\InfoboxRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Service\ContingentDataService; +use EP\EpProducts\Service\DateService; +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\MyEpUrlResolverService; +use EP\EpProducts\Service\ResellerDataService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use League\Period\Period; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +class AjaxTableController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @var InfoboxRepository + */ + protected $infoboxRepository; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var DateService + */ + protected $dateService; + + /** + * @var ResellerDataService + */ + protected $resellerDataService; + + /** + * @var ContingentDataService + */ + protected $contingentDataService; + + /** + * @var MyEpUrlResolverService + */ + protected $myEpUrlResolverService; + + /** + * @param ProductRepository $productRepository + * @param InfoboxRepository $infoboxRepository + * @param DateService $dateService + * @param FilterService $filterService + * @param ResellerDataService $resellerDataService + * @param ContingentDataService $contingentDataService + * @param MyEpUrlResolverService $myEpUrlResolverService + */ + public function __construct( + ProductRepository $productRepository, + InfoboxRepository $infoboxRepository, + DateService $dateService, + FilterService $filterService, + ResellerDataService $resellerDataService, + ContingentDataService $contingentDataService, + MyEpUrlResolverService $myEpUrlResolverService + ) { + $this->productRepository = $productRepository; + $this->infoboxRepository = $infoboxRepository; + $this->dateService = $dateService; + $this->filterService = $filterService; + $this->resellerDataService = $resellerDataService; + $this->contingentDataService = $contingentDataService; + $this->myEpUrlResolverService = $myEpUrlResolverService; + } + + public function initializeDatesAction() + { + if ($this->request->hasArgument('dateFrom')) { + $dateFrom = $this->request->getArgument('dateFrom'); + $this->request->setArgument('dateFrom', $dateFrom === '' ? null : $dateFrom); + } + if ($this->request->hasArgument('dateTo')) { + $dateTo = $this->request->getArgument('dateTo'); + $this->request->setArgument('dateTo', $dateTo === '' ? null : $dateTo); + } + } + + /** + * @param Product $product + * @param Hotel $hotel + * @param string|null $dateFrom + * @param string|null $dateTo + */ + public function datestableAction( + Product $product, + Hotel $hotel, + string $dateFrom = null, + string $dateTo = null + ): void { + $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); + $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids); + $filterSettings['dateFrom'] = $dateFrom; + $filterSettings['dateTo'] = $dateTo; + $paCode = GeneralUtility::_GET('pa'); + $myEpUrl = $this->myEpUrlResolverService->resolve(); + + $dates = $this->dateService->getDatesTable([ + 'product' => $product, + 'hotel' => $hotel, + 'filterSettings' => $filterSettings, + ]); + + if (1 === count($dates)) { + $date = reset($dates); + $this->forward( + 'pricetable', + null, + 'ep_products', + [ + 'product' => $product, + 'hotel' => $hotel, + 'date' => $date['dateUid'], + 'paCode' => $paCode, + 'singleDate' => true, + ] + ); + } + + $nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true); + + $allDates = $this->dateService->preprocessDatesTable([ + 'dateTableData' => $dates, + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + 'showBookingButton' => false === $product->getHideBookingButton(), + 'nonBookableDateUids' => $nonBookableDateUids, + ]); + + $bookableDates = array_filter($allDates, function (array $date) { + return $date['available'] > 0; + }); + $includedServices = $this->dateService->groupIncludedServices($bookableDates); + + $altProductLink = null; + $altLabel = null; + if ($product->getAltProduct()) { + $altProduct = $product->getAltProduct(); + if ($this->productRepository->hasDates($altProduct)) { + /** @var ContentObjectRenderer $contentObject */ + $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class); + $altProductLink = $contentObject->typoLink_URL( + [ + 'parameter' => $altProduct->getDetailPage(), + ] + ); + $altLabel = $product->getAltLabel(); + } + } + + $hasSurchargeOrDiscount = $this->dateService->checkSurcharcheOrDiscount($allDates); +// $hasIncludedServices = $this->dateService->hasIncludedServices($allDates); + $hasIncludedServices = true; + $noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids']); + $isProductWithNoInfoConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(), + $noInfoConceptUids, false); + + $nonBookableInfo = $this->getNonBookableInfobox($product); + + $this->view->assignMultiple([ + 'allDates' => $allDates, + 'bookableDates' => $bookableDates, + 'includedServicesAllDates' => $includedServices['allDates'], + 'includedServicesSomeDates' => $includedServices['someDates'], + 'bookable' => $product->getBookable(), + 'unavailable' => 0 === count($allDates), + 'product' => $product, + 'hotel' => $hotel, + 'altProductLink' => $altProductLink, + 'altLabel' => $altLabel, + 'hasIncludedServices' => $hasIncludedServices, + 'hasSurcharge' => $hasSurchargeOrDiscount['surcharge'], + 'hasDiscount' => $hasSurchargeOrDiscount['discount'], + 'isProductWithNoInfoConcept' => $isProductWithNoInfoConcept, + 'nonBookableInfo' => $nonBookableInfo, + 'myEpUrl' => $myEpUrl, + ]); + } + + /** + * @param Product $product + * @param Hotel $hotel + * @param Date $date + * @param bool $singleDate + */ + public function pricetableAction( + Product $product, + Hotel $hotel, + Date $date, + bool $singleDate = false + ): void { + $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); + $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids); + $myEpUrl = $this->myEpUrlResolverService->resolve(); + + $priceTable = $this->dateService->getPriceTable([ + 'product' => $product, + 'hotel' => $hotel, + 'filterSettings' => $filterSettings, + 'date' => $date, + 'myEpUrl' => $myEpUrl, + 'paCode' => GeneralUtility::_GET('pa'), + ]); + $hasSurchargeOrDiscount = $this->dateService->checkSurcharcheOrDiscount($priceTable); + $hasIncludedServices = $this->dateService->hasIncludedServices($priceTable); + + $dateStart = $date ? $date->getDateStart()->format('d.m.y') : ''; + $dateEnd = $date ? $date->getDateEnd()->format('d.m.y') : ''; + $dateRange = sprintf('%s - %s', $dateStart, $dateEnd); + + $noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids']); + $isProductWithNoInfoConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(), $noInfoConceptUids); + + $nonBookableInfo = $this->getNonBookableInfobox($product); + + $this->view->assignMultiple([ + 'dateRange' => $dateRange, + 'product' => $product, + 'hotel' => $hotel, + 'servicesIncluded' => $date->getServicesIncluded(), + 'prices' => $priceTable, + 'unavailable' => 0 === count($priceTable), + 'hasIncludedServices' => $hasIncludedServices, + 'hasSurcharge' => $hasSurchargeOrDiscount['surcharge'], + 'hasDiscount' => $hasSurchargeOrDiscount['discount'], + 'isProductWithNoInfoConcept' => $isProductWithNoInfoConcept, + 'isSingleDate' => $singleDate, + 'nonBookableInfo' => $nonBookableInfo, + 'myEpUrl' => $myEpUrl, + ]); + } + + /** + * @param Product $product + * @param Hotel $hotel + */ + public function pricetableHtmlAction(Product $product, Hotel $hotel): void + { + $nonBookableInfo = $this->getNonBookableInfobox($product); + $myEpUrl = $this->myEpUrlResolverService->resolve(); + + $priceTable = $this->dateService->getPriceTable([ + 'product' => $product, + 'hotel' => $hotel, + 'filterSettings' => $this->filterService->getNormalizedFilterSettings(), + 'myEpUrl' => $myEpUrl, + 'paCode' => GeneralUtility::_GET('pa'), + 'sortByRoom' => true, + ]); + $hasIncludedServices = $this->dateService->hasIncludedServices($priceTable); + + $this->view->assignMultiple([ + 'pricetable' => $priceTable, + 'hasIncludedServices' => $hasIncludedServices, + 'nonBookableInfo' => $nonBookableInfo, + 'myEpUrl' => $myEpUrl, + ]); + } + + /** + * @param Product $product + */ + public function eventPricetableAction(Product $product): void + { + $roomTypes = GeneralUtility::trimExplode(',', $this->settings['priceTableRoomTypes']); + $roomTypesPax = array_map(function ($type) { + return (int) $type; + }, $roomTypes); + $roomTypesLabels = array_map(function ($type) { + $label = (int) $type . 'er'; + if (false !== strpos($type, '+')) { + $label .= '+'; + } + return $label; + }, $roomTypes); + $maxPax = max($roomTypesPax); + $myEpUrl = $this->myEpUrlResolverService->resolve(); + $priceTable = $this->dateService->getEventPriceTable($product, $maxPax, $myEpUrl); + $hasIncludedServices = $this->dateService->hasIncludedServices($priceTable); + $nonBookableInfo = $this->getNonBookableInfobox($product); + $categories = [ + Hotel::CATEGORY_STANDARD => 'Standard', + Hotel::CATEGORY_COMFORT => 'Komfort', + Hotel::CATEGORY_DELUXE => 'Deluxe', + Hotel::CATEGORY_SUPER_DELUXE => 'Super Deluxe', + ]; + $this->view->assignMultiple([ + 'roomTypesPax' => $roomTypesPax, + 'roomTypesLabels' => $roomTypesLabels, + 'pricetable' => $priceTable, + 'hasIncludedServices' => $hasIncludedServices, + 'categories' => $categories, + 'themekey' => $this->settings['themekey'], + 'nonBookableInfo' => $nonBookableInfo, + 'myEpUrl' => $myEpUrl, + ]); + } + + /** + * @param Hotel $hotel + * @param Product $product + * @param string $dateFrom + * @param string $dateTo + */ + public function daytripRoomsAction( + Hotel $hotel, + Product $product, + string $dateFrom, + string $dateTo + ): void { + $period = new Period($dateFrom, $dateTo); + $nights = (int) $period->dateInterval()->format('%a') + 1; + $myEpUrl = $this->myEpUrlResolverService->resolve(); + + if (2 > $nights) { + $rooms = []; + } else { + $rooms = $this + ->contingentDataService + ->getAvailableRooms($dateFrom, $dateTo, $hotel, $product, $myEpUrl); + } + + $this->view->assign('nights', $nights); + $this->view->assign('rooms', $rooms); + $this->view->assign('myEpUrl', $myEpUrl); + } + + /** + * @param Product $product + * @return \EP\EpProducts\Domain\Model\Infobox + */ + protected function getNonBookableInfobox(Product $product) + { + if (null !== $nonBookableInfo = $product->getNonBookableInfo()) { + return $nonBookableInfo; + } + + return $this->infoboxRepository->getDefault(); + } +} diff --git a/packages/ep_products/Classes/Controller/AjaxTeaserPopupController.php b/packages/ep_products/Classes/Controller/AjaxTeaserPopupController.php new file mode 100644 index 0000000..a97b2e3 --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxTeaserPopupController.php @@ -0,0 +1,17 @@ +view->assignMultiple([ + 'concept' => $concept, + 'targetUrl' => $targetUrl, + ]); + } +} diff --git a/packages/ep_products/Classes/Controller/AjaxWatchlistController.php b/packages/ep_products/Classes/Controller/AjaxWatchlistController.php new file mode 100644 index 0000000..396939a --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxWatchlistController.php @@ -0,0 +1,104 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\SearchResultService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class AjaxWatchlistController extends ActionController +{ + + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @var SearchResultService + */ + protected $searchResultService; + + /** + * @var FilterService + */ + protected $filterService; + + /** + * @param ProductRepository $productRepository + * @param SearchResultService $searchResultService + * @param FilterService $filterService + */ + public function __construct + ( + ProductRepository $productRepository, + SearchResultService $searchResultService, + FilterService $filterService + ) + { + $this->productRepository = $productRepository; + $this->searchResultService = $searchResultService; + $this->filterService = $filterService; + } + + public function initializeAction() + { + if ($this->request->hasArgument('watchlistUids')) { + $watchlistUids = json_decode($this->request->getArgument('watchlistUids'), true, 512, JSON_THROW_ON_ERROR); + $this->request->setArgument('watchlistUids', $watchlistUids); + } + } + + /** + * @param array $watchlistUids + * @return string + * @throws \Doctrine\DBAL\DBALException + */ + public function listAction(array $watchlistUids = []) + { + $productUids = array_map(function ($uid) { + [ , $productUid ] = GeneralUtility::trimExplode(':', $uid); + return $productUid; + }, $watchlistUids); + + $hotelUids = array_map(function ($uid) { + [ $hotelUid, ] = GeneralUtility::trimExplode(':', $uid); + return $hotelUid; + }, $watchlistUids); + + $watchlistData = $this->productRepository->getWatchlist($productUids, $hotelUids); + $watchlist = $this->searchResultService->preprocess($watchlistData); + + $this->view->assignMultiple([ + 'watchlist' => $watchlist->getData(), + 'total' => $watchlist->getTotal() + ]); + } +} diff --git a/packages/ep_products/Classes/Controller/BackendController.php b/packages/ep_products/Classes/Controller/BackendController.php new file mode 100644 index 0000000..d92a2a7 --- /dev/null +++ b/packages/ep_products/Classes/Controller/BackendController.php @@ -0,0 +1,149 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\ContingentImportService; +use EP\EpProducts\Service\DateImportService; +use EP\EpProducts\Service\ImportTimestampService; +use EP\EpProducts\Service\LogService; +use EP\EpProducts\Service\SnowreportImportService; +use TYPO3\CMS\Backend\View\BackendTemplateView; +use TYPO3\CMS\Core\Messaging\AbstractMessage; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class BackendController extends ActionController +{ + /** + * @var BackendTemplateView + */ + protected $defaultViewObjectName = BackendTemplateView::class; + + /** + * @var BackendTemplateView + */ + protected $view; + + /** + * @var \EP\EpProducts\Service\DateImportService + */ + protected $dateImportService; + + /** + * @var \EP\EpProducts\Service\SnowreportImportService + */ + protected $snowreportImportService; + + /** + * @var \EP\EpProducts\Service\LogService + */ + protected $logService; + + /** + * @var ContingentImportService + */ + protected $contingentImportService; + + /** + * @param DateImportService $dateImportService + * @param ContingentImportService $contingentImportService + * @param SnowreportImportService $snowreportService + * @param LogService $logService + */ + public function __construct + ( + DateImportService $dateImportService, + ContingentImportService $contingentImportService, + SnowreportImportService $snowreportService, + LogService $logService + ) + { + $this->dateImportService = $dateImportService; + $this->contingentImportService = $contingentImportService; + $this->snowreportImportService = $snowreportService; + $this->logService = $logService; + } + + public function indexAction() + { + $timestampService = new ImportTimestampService(); + + $productDataUpdatedTimestamp = $timestampService->readImportTimestamp('fileadmin/products_import.info'); + $contingentsDataUpdatedTimestamp = $timestampService->readImportTimestamp('fileadmin/contingents_import.info'); + + $this->view->assign('productDataUpdatedTimestamp', $productDataUpdatedTimestamp); + $this->view->assign('contingentsDataUpdatedTimestamp', $contingentsDataUpdatedTimestamp); + } + + public function importSnowReportAction() + { + $this->snowreportImportService->downloadReportsXml(); + $count = $this->snowreportImportService->importSnowReportData(); + $this->addFlashMessage($count . ' Datensätze importiert', 'Import abgechlossen', AbstractMessage::OK); + $this->redirect('index'); + } + + public function dumpSearchLogEpreisenAction() + { + $csv = $this->logService->dumpSearchLogEpreisen(); + $this->dumpSearchLog($csv, 'epreisen'); + } + + public function dumpSearchLogEpeventsAction() + { + $csv = $this->logService->dumpSearchLogEpevents(); + $this->dumpSearchLog($csv, 'epevents'); + } + + /** + * @param array $csv + * @param $name + */ + private function dumpSearchLog(array $csv, $name) + { + $fileName = sprintf('searchlog_dump_%s_%s.csv', $name, date('dmY')); + + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Content-Description: File Transfer'); + header('Content-type: text/csv'); + header("Content-Disposition: attachment; filename={$fileName}"); + header('Expires: 0'); + header('Pragma: public'); + + $fh = @fopen( 'php://output', 'wb' ); + + foreach ($csv as $data) { + fputcsv($fh, $data, ';'); + } + + fclose($fh); + + exit; + } + +} diff --git a/packages/ep_products/Classes/Controller/CityController.php b/packages/ep_products/Classes/Controller/CityController.php new file mode 100644 index 0000000..2e3c869 --- /dev/null +++ b/packages/ep_products/Classes/Controller/CityController.php @@ -0,0 +1,97 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\City; +use EP\EpProducts\Domain\Repository\CityRepository; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class CityController extends ActionController +{ + + /** + * @var CityRepository + */ + protected $cityRepository; + + /** + * @var ProductDataService + */ + private $productDataService; + + /** + * @param CityRepository $cityRepository + * @param ProductDataService $productDataService + */ + public function __construct(CityRepository $cityRepository, ProductDataService $productDataService) + { + $this->cityRepository = $cityRepository; + $this->productDataService = $productDataService; + } + + /** + * @param City $city + */ + public function detailAction(City $city = null): void + { + if (null === $city) { + $cityUid = $this->settings['cityUid']; + $city = $this->cityRepository->findByIdentifier($cityUid); + } + + $teasers = []; + + if (null !== $city) { + $filterSettings = [ + 'cityUids' => [$cityUid] + ]; + $teasers = $this->productDataService->getTeasergroup($filterSettings); + } + + $this->view->assignMultiple([ + 'city' => $city, + 'teasers' => $teasers, + 'filterSettings' => $filterSettings, + ]); + } + + public function teasergroupAction(): void + { + $regionUid = (int)$this->settings['regionUid']; + $countryUid = (int)$this->settings['countryUid']; + $season = $this->settings['season']; + $citiesPid = (int)$this->settings['citiesStoragePid']; + + $cities = $this->cityRepository->getTeasersByCountryOrRegionAndSeason($regionUid, $countryUid, $season, $citiesPid); + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('teasers', $cities); + } + +} diff --git a/packages/ep_products/Classes/Controller/ConceptController.php b/packages/ep_products/Classes/Controller/ConceptController.php new file mode 100644 index 0000000..82a3796 --- /dev/null +++ b/packages/ep_products/Classes/Controller/ConceptController.php @@ -0,0 +1,104 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Concept; +use EP\EpProducts\Domain\Repository\ConceptRepository; +use EP\EpProducts\Service\ProductDataService; +use EP\EpTheme\Domain\Repository\PageRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class ConceptController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\ConceptRepository + */ + protected $conceptRepository; + + /** + * @var \EP\EpProducts\Service\ProductDataService + */ + protected $productDataService; + + /** + * @var \EP\EpTheme\Domain\Repository\PageRepository + */ + private $pageRepository; + + /** + * @param ConceptRepository $conceptRepository + * @param ProductDataService $productDataService + * @param PageRepository $pageRepository + */ + public function __construct(ConceptRepository $conceptRepository, ProductDataService $productDataService, PageRepository $pageRepository) + { + $this->conceptRepository = $conceptRepository; + $this->productDataService = $productDataService; + $this->pageRepository = $pageRepository; + } + + /** + * @param Concept $concept + */ + public function detailAction(Concept $concept = null) + { + if ($concept === null) { + $conceptUid = $this->settings['conceptUid']; + $concept = $this->conceptRepository->findByIdentifier($conceptUid); + } + + $teaserConceptUids = GeneralUtility::trimExplode(',', $this->settings['additionalConceptUids']); + if (null !== $concept) { + array_unshift($teaserConceptUids, $concept->getUid()); + } + + $filterSettings = ['conceptUids' => $teaserConceptUids]; + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $limit = $this->settings['showMoreLink'] ? 6 : 0; + $teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit); + $pages = $this->pageRepository->findPagesByPid((int)$this->settings['conceptsPageUid']); + $data = $this->configurationManager->getContentObject()->data; + + $this->view->assignMultiple([ + 'teasers' => $teasers, + 'concept' => $concept, + 'filterSettings' => $filterSettings, + 'pages' => $pages, + 'data' => $data, + ]); + } + + public function teasergroupAction() + { + $concepts = $this->conceptRepository->findAll(); + $this->view->assign('teasers', $concepts); + } + +} diff --git a/packages/ep_products/Classes/Controller/CountryController.php b/packages/ep_products/Classes/Controller/CountryController.php new file mode 100644 index 0000000..89cf743 --- /dev/null +++ b/packages/ep_products/Classes/Controller/CountryController.php @@ -0,0 +1,79 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Country; +use EP\EpProducts\Domain\Repository\CountryRepository; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class CountryController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\CountryRepository + */ + protected $countryRepository; + + /** + * @var ProductDataService + */ + protected $productDataService; + + /** + * @param CountryRepository $countryRepository + * @param ProductDataService $productDataService + */ + public function __construct(CountryRepository $countryRepository, ProductDataService $productDataService) + { + $this->countryRepository = $countryRepository; + $this->productDataService = $productDataService; + } + + /** + * @param Country $country + */ + public function detailAction(Country $country = null) + { + if ($country === null) { + $countryUid = $this->settings['countryUid']; + $country = $this->countryRepository->findByIdentifier($countryUid); + } + $filterSettings = [ + 'countryUids' => [ $countryUid ], + ]; + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $limit = $this->settings['showMoreLink'] ? 6 : 0; + $teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit); + $this->view->assign('teasers', $teasers); + $this->view->assign('country', $country); + $this->view->assign('filterSettings', $filterSettings); + } + +} diff --git a/packages/ep_products/Classes/Controller/FaqController.php b/packages/ep_products/Classes/Controller/FaqController.php new file mode 100644 index 0000000..d919da1 --- /dev/null +++ b/packages/ep_products/Classes/Controller/FaqController.php @@ -0,0 +1,56 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Repository\FaqRepository; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class FaqController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\FaqRepository + */ + protected $faqRepository; + + /** + * @param FaqRepository $faqRepository + */ + public function __construct(FaqRepository $faqRepository) + { + $this->faqRepository = $faqRepository; + } + + public function detailAction() + { + $faqUids = $this->settings['faqUids']; + $faqs = $this->faqRepository->findByUids($faqUids); + $this->view->assign('faqs', $faqs); + } + +} diff --git a/packages/ep_products/Classes/Controller/HotelController.php b/packages/ep_products/Classes/Controller/HotelController.php new file mode 100644 index 0000000..ce1d22b --- /dev/null +++ b/packages/ep_products/Classes/Controller/HotelController.php @@ -0,0 +1,152 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Repository\HotelRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Service\HotelDataService; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class HotelController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\HotelRepository + */ + protected $hotelRepository; + + /** + * @var \EP\EpProducts\Domain\Repository\ProductRepository + */ + protected $productRepository; + + /** + * @var \EP\EpProducts\Service\HotelDataService + */ + protected $hotelDataService; + + /** + * @var ProductDataService + */ + protected $productDataService; + + /** + * @param HotelDataService $hotelDataService + * @param HotelRepository $hotelRepository + * @param ProductRepository $productRepository + * @param ProductDataService $productDataService + */ + public function __construct + ( + HotelDataService $hotelDataService, + HotelRepository $hotelRepository, + ProductRepository $productRepository, + ProductDataService $productDataService + ) + { + $this->hotelRepository = $hotelRepository; + $this->productRepository = $productRepository; + $this->hotelDataService = $hotelDataService; + $this->productDataService = $productDataService; + } + + /** + * @param Hotel $hotel + */ + public function detailAction(Hotel $hotel = null) + { + if ($hotel !== null) { + $hotelUid = $hotel->getUid(); + } else { + $hotelUid = $this->settings['hotelUid']; + /** @var Hotel $hotel */ + $hotel = $this->hotelRepository->findByIdentifier($hotelUid); + } + $filterSettings = [ + 'hotelUid' => $hotelUid, + ]; + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $limit = $this->settings['hideMoreLink'] ? 0 : 6; + $teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit); + $this->view->assign('hotel', $hotel); + $this->view->assign('teasers', $teasers); + $this->view->assign('filterSettings', $filterSettings); + } + + public function detailEventAction() + { + $productUid = $this->settings['productUid']; + /** @var \EP\EpProducts\Domain\Model\Product $product */ + $product = $this->productRepository->findByIdentifier($productUid); + $hotelUid = $this->settings['hotelUid']; + /** @var \EP\EpProducts\Domain\Model\Hotel $hotel */ + $hotel = $this->hotelRepository->findByIdentifier($hotelUid); + $this->view->assignMultiple([ + 'product' => $product, + 'hotel' => $hotel, + ]); + } + + public function teasergroupAction() + { + $criteria = [ + 'regionUid' => (int) $this->settings['regionUid'], + 'countryUid' => (int) $this->settings['countryUid'], + 'conceptUid' => (int) $this->settings['conceptUid'], + 'productUid' => (int) $this->settings['productUid'], + 'category' => (int) $this->settings['category'], + 'type' => (int) $this->settings['type'], + ]; + $bookable = $this->hotelDataService->getTeasersByCriteria($criteria); + $bookableHotelUids = array_keys($bookable); + $nonBookable = $this->hotelDataService->getDateIndependentTeasersByCriteria($criteria, $bookableHotelUids); + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('bookable', $bookable); + $this->view->assign('nonBookable', $nonBookable); + } + + public function dateIndependentTeasergroupAction() + { + $criteria = [ + 'regionUid' => (int) $this->settings['regionUid'], + 'countryUid' => (int) $this->settings['countryUid'], + 'productUid' => (int) $this->settings['productUid'], + 'category' => (int) $this->settings['category'], + 'type' => (int) $this->settings['type'], + ]; + $hotels = $this->hotelDataService->getDateIndependentTeasersByCriteria($criteria); + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('teasers', $hotels); + } + +} diff --git a/packages/ep_products/Classes/Controller/IcalController.php b/packages/ep_products/Classes/Controller/IcalController.php new file mode 100644 index 0000000..46675b0 --- /dev/null +++ b/packages/ep_products/Classes/Controller/IcalController.php @@ -0,0 +1,56 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Service\IcalService; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class IcalController extends ActionController +{ + /** + * @var IcalService + */ + protected $icalService; + + public function __construct(IcalService $icalService) + { + $this->icalService = $icalService; + } + + /** + * @param Hotel $hotel + * @return string + */ + public function contingentsAction(Hotel $hotel) + { + header(sprintf('Content-disposition: attachment; filename=%s.ical', $hotel->getPathSegment())); + + return $this->icalService->createContingentsFeed($hotel); + } +} diff --git a/packages/ep_products/Classes/Controller/MapController.php b/packages/ep_products/Classes/Controller/MapController.php new file mode 100644 index 0000000..612f6f7 --- /dev/null +++ b/packages/ep_products/Classes/Controller/MapController.php @@ -0,0 +1,123 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Repository\HotelRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +class MapController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\HotelRepository + */ + protected $hotelRepository; + + /** + * @var \EP\EpProducts\Domain\Repository\ProductRepository + */ + protected $productRepository; + + /** + * @param HotelRepository $hotelRepository + * @param ProductRepository $productRepository + */ + public function __construct( + HotelRepository $hotelRepository, + ProductRepository $productRepository + ) + { + $this->hotelRepository = $hotelRepository; + $this->productRepository = $productRepository; + } + + public function generalAction() + { + $markerData = []; + $selectedHotels = $this->settings['hotelUids']; + $hotelUids = GeneralUtility::trimExplode(',', $selectedHotels); + $hotels = $this->hotelRepository->getListByUids($hotelUids); + if ($hotels) { + foreach ($hotels as $hotel) + { + /** @var \EP\EpProducts\Domain\Model\Hotel $hotel */ + $markerData[] = [ + 'name' => $hotel->getName(), + 'uri' => $this->getUriForPageUid($hotel->getDetailPage()), + 'teaser' => $hotel->getTeaser(), + 'lat' => $hotel->getLatitude(), + 'lng' => $hotel->getLongitude(), + ]; + } + } + + $selectedProducts = $this->settings['productUids']; + $productUids = GeneralUtility::trimExplode(',', $selectedProducts); + $products = $this->productRepository->getListByUids($productUids); + if ($products) { + foreach ($products as $product) + { + /** @var \EP\EpProducts\Domain\Model\Product $product */ + $hotel = $product->getHotels()->current(); + if (null === $hotel || false === $hotel->isShowAsTeaser()) { + continue; + } + $markerData[] = [ + 'name' => $product->getName(), + 'uri' => $this->getUriForPageUid($product->getDetailPage()), + 'externalLink' => $hotel->getExternalLink(), + 'teaser' => preg_replace("/[\r\n\t]/", "", $product->getTeaser()), + 'lat' => $hotel->getLatitude(), + 'lng' => $hotel->getLongitude(), + ]; + } + } + + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('markerData', json_encode($markerData, JSON_THROW_ON_ERROR | JSON_HEX_APOS)); + } + + /** + * @param int $pageUid + * @return string + */ + protected function getUriForPageUid($pageUid) + { + /** @var ContentObjectRenderer $contentObject */ + $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class); + return $contentObject->typoLink_URL( + [ + 'parameter' => $pageUid, + ] + ); + } +} diff --git a/packages/ep_products/Classes/Controller/NewsletterController.php b/packages/ep_products/Classes/Controller/NewsletterController.php new file mode 100644 index 0000000..07c4ae4 --- /dev/null +++ b/packages/ep_products/Classes/Controller/NewsletterController.php @@ -0,0 +1,80 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Dto\NewsletterSubscriptionRequest; +use EP\EpProducts\MyEP\ApiClient; +use EP\EpProducts\MyEP\ApiException; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class NewsletterController extends ActionController +{ + private ApiClient $apiClient; + + public function __construct(ApiClient $apiClient) + { + $this->apiClient = $apiClient; + } + + public function indexAction() + { + $this->view->assign('result', GeneralUtility::_GET('result')); + } + + public function subscriptionFormAction(?NewsletterSubscriptionRequest $subscriptionRequest = null) + { + if (null === $subscriptionRequest) { + $subscriptionRequest = new NewsletterSubscriptionRequest(); + } + + try { + $lists = $this->apiClient->getNewsletterLists(); + } catch (ApiException $e) { + $lists = []; + } + + $this->view->assign('subscriptionRequest', $subscriptionRequest); + $this->view->assign('lists', $lists); + } + + public function subscriptionFormSubmitAction(NewsletterSubscriptionRequest $subscriptionRequest) + { + try { + $result = $this->apiClient->subscribeToNewsletters($subscriptionRequest); + $result['success'] = true; + } catch (ApiException $e) { + $result = [ + 'success' => false, + 'message' => $e->getMessage(), + ]; + } + + $this->view->assign('result', $result); + } +} diff --git a/packages/ep_products/Classes/Controller/PricetableController.php b/packages/ep_products/Classes/Controller/PricetableController.php new file mode 100644 index 0000000..3271299 --- /dev/null +++ b/packages/ep_products/Classes/Controller/PricetableController.php @@ -0,0 +1,41 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class PricetableController extends ActionController +{ + + public function eventAction() + { + $productUid = $this->settings['productUid']; + $this->view->assign('productUid', $productUid); + } + +} diff --git a/packages/ep_products/Classes/Controller/ProductController.php b/packages/ep_products/Classes/Controller/ProductController.php new file mode 100644 index 0000000..efb7411 --- /dev/null +++ b/packages/ep_products/Classes/Controller/ProductController.php @@ -0,0 +1,261 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\HotelRepository; +use EP\EpProducts\Domain\Repository\InfoboxRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Service\ContingentDataService; +use EP\EpProducts\Service\DateService; +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\HotelDataService; +use EP\EpProducts\Service\PickupOverviewService; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class ProductController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\ProductRepository + */ + protected $productRepository; + + /** + * @var \EP\EpProducts\Domain\Repository\HotelRepository + */ + protected $hotelRepository; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var \EP\EpProducts\Service\ProductDataService + */ + protected $productDataService; + + /** + * @var DateService + */ + protected $dateService; + + /** + * @var HotelDataService + */ + protected $hotelDataService; + /** + * @var ContingentDataService + */ + private $contingentDataService; + + /** + * @var InfoboxRepository + */ + private $infoboxRepository; + + /** + * @var PickupOverviewService + */ + private $pickupOverviewService; + + /** + * @param ProductRepository $productRepository + * @param ProductDataService $productDataService + * @param HotelRepository $hotelRepository + * @param HotelDataService $hotelDataService + * @param ContingentDataService $contingentDataService + * @param DateService $dateService + * @param FilterService $filterService + * @param InfoboxRepository $infoboxRepository + * @param PickupOverviewService $pickupOverviewService + */ + public function __construct( + ProductRepository $productRepository, + ProductDataService $productDataService, + HotelRepository $hotelRepository, + HotelDataService $hotelDataService, + ContingentDataService $contingentDataService, + DateService $dateService, + FilterService $filterService, + InfoboxRepository $infoboxRepository, + PickupOverviewService $pickupOverviewService + ) { + $this->productRepository = $productRepository; + $this->productDataService = $productDataService; + $this->hotelRepository = $hotelRepository; + $this->hotelDataService = $hotelDataService; + $this->contingentDataService = $contingentDataService; + $this->dateService = $dateService; + $this->filterService = $filterService; + $this->infoboxRepository = $infoboxRepository; + $this->pickupOverviewService = $pickupOverviewService; + } + + /** + * @param Product $product + * @param Hotel $hotel + * @param string $dateFrom + * @param string $dateTo + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException + */ + public function detailAction( + Product $product = null, + Hotel $hotel = null, + $dateFrom = null, + $dateTo = null + ): void { + // Get product to display from flexform settings if not provided via url + if ($product === null) { + $productUid = $this->settings['productUid']; + $product = $this->productRepository->findByIdentifier($productUid); + } + // Redirect to search page in case no product can be determined + if ($product === null) { + $filterSettings = $this->filterService->getNormalizedFilterSettings(); + $this->redirect( + 'searchresult', + 'Search', + null, + ['filterSettings' => $filterSettings], + $this->settings['defaultSearchPageUid'] + ); + } + // Get hotel to display from flexform settings if not provided via url + if ((int)$this->settings['hotelUid'] > 0) { + $hotelUid = $this->settings['hotelUid']; + $hotel = $this->hotelRepository->findByIdentifier($hotelUid); + } + if ($hotel === null) { + // Forward to hotel list action in case multiple hotels are assigned to this product + if ($product->getHotels()->count() > 1) { + $arguments = [ + 'product' => $product, + ]; + $this->forward('hotelList', null, null, $arguments); + } + // Use first hotel which should be the only one at this point + if ($product->getHotels()->count() > 0) { + $product->getHotels()->rewind(); + $hotel = $product->getHotels()->current(); + } + } + + if ($product->isDaytrip()) { + $availableContingents = $this + ->contingentDataService + ->getAvailableContingents($hotel, $product) + ; + $enabledDates = array_keys($availableContingents); + } + + $concept = $product->getConcept(); + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $isProductWithExcludedConcept = ($concept !== null) && \in_array($concept->getUid(), $excludedConceptUids); + $noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids'], true); + $isProductWithNoInfoConcept = ($concept !== null) && \in_array($concept->getUid(), $noInfoConceptUids); + $groupInquiryFormConceptUids = GeneralUtility::trimExplode(',', $this->settings['groupInquiryFormConceptUids'], true); + $isProductWithGroupInquiryForm = ($concept !== null) && \in_array($concept->getUid(), $groupInquiryFormConceptUids); + $nonBookableInfo = $product->getNonBookableInfo(); + + // Assemble backlink + $backlinkUrl = GeneralUtility::_GET('ref'); + $backLinkLabel = strpos($backlinkUrl, 'suche') !== -1 ? 'zurück zum Suchergebnis' : 'zurück'; + + $this->view->assignMultiple([ + 'hotel' => $hotel, + 'product' => $product, + 'enabledDates' => $enabledDates ?? [], + 'isProductWithExcludedConcept' => $isProductWithExcludedConcept, + 'isProductWithNoInfoConcept' => $isProductWithNoInfoConcept, + 'isProductWithGroupInquiryForm' => $isProductWithGroupInquiryForm, + 'currentPageUid' => $GLOBALS['TSFE']->id, + 'dateFrom' => $dateFrom, + 'dateTo' => $dateTo, + 'referringUrl' => $backlinkUrl, + 'backlinkLabel' => $backLinkLabel, + 'nonBookableInfo' => $nonBookableInfo, + 'pickupsOverview' => $this->pickupOverviewService->getOverview($product, $hotel), + ]); + } + + public function pricetableAction(): void + { + $productUid = $this->settings['productUid']; + /** @var \EP\EpProducts\Domain\Model\Product $product */ + $product = $this->productRepository->findByIdentifier($productUid); + $hotelUid = $this->settings['hotelUid']; + /** @var \EP\EpProducts\Domain\Model\Hotel $hotel */ + $hotel = $this->hotelRepository->findByIdentifier($hotelUid); + $pricetable = $this->dateService->getPriceTable([ + 'product' => $product, + 'hotel' => $hotel, + 'filterSettings' => $this->filterService->getNormalizedFilterSettings(), + ]); + $this->view->assign('pricetable', $pricetable); + } + + /** + * @param Product $product + */ + public function hotelListAction(Product $product): void + { + $hotels = $this->hotelDataService->getList($product); + $this->view->assignMultiple([ + 'hotels' => $hotels, + 'product' => $product, + 'linkHotel' => true, + ]); + } + + public function teasergroupAction(): void + { + $filterSettings = $this->filterService->getFiltersettingsFromSettings($this->settings); + $dateAuto = !$this->settings['dateFrom'] && !$this->settings['dateTo']; + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $limit = $this->settings['hideMoreLink'] ? 0 : 6; + $teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit); + $dateFrom = $this->settings['dateFrom'] ? date('Y-m-d', $this->settings['dateFrom']) : null; + $dateTo = $this->settings['dateTo'] ? date('Y-m-d', $this->settings['dateTo']) : null; + $data = $this->configurationManager->getContentObject()->data; + $nonBookableInfo = $this->settings['infoboxUid'] ? $this->infoboxRepository->findByUid($this->settings['infoboxUid']) : $this->infoboxRepository->getDefault(); + + $this->view->assign('data', $data); + $this->view->assign('teasers', $teasers); + $this->view->assign('dateAuto', $dateAuto); + $this->view->assign('filterSettings', $filterSettings); + $this->view->assign('dateFrom', $dateFrom); + $this->view->assign('dateTo', $dateTo); + $this->view->assign('nonBookableInfo', $nonBookableInfo); + } +} diff --git a/packages/ep_products/Classes/Controller/RegionController.php b/packages/ep_products/Classes/Controller/RegionController.php new file mode 100644 index 0000000..e605507 --- /dev/null +++ b/packages/ep_products/Classes/Controller/RegionController.php @@ -0,0 +1,95 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Region; +use EP\EpProducts\Domain\Repository\RegionRepository; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class RegionController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\RegionRepository + */ + protected $regionRepository; + + /** + * @var \EP\EpProducts\Service\ProductDataService + */ + protected $productDataService; + + /** + * @param RegionRepository $regionRepository + * @param ProductDataService $productDataService + */ + public function __construct(RegionRepository $regionRepository, ProductDataService $productDataService) + { + $this->regionRepository = $regionRepository; + $this->productDataService = $productDataService; + } + + /** + * @param Region $region + */ + public function detailAction(Region $region = null) + { + if ($region === null) { + $regionUid = $this->settings['regionUid']; + $region = $this->regionRepository->findByIdentifier($regionUid); + } + $additionalRegionUids = GeneralUtility::trimExplode(',', $this->settings['additionalRegionUids'], true); + $additionalRegionUids = array_map(function ($uid) { + return (int)$uid; + }, $additionalRegionUids); + $teaserRegionUids = array_merge([$region->getUid()], $additionalRegionUids); + $filterSettings = [ + 'regionUids' => $teaserRegionUids, + ]; + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $limit = $this->settings['hideMoreLink'] ? 0 : 6; + $teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit); + $this->view->assign('teasers', $teasers); + $this->view->assign('region', $region); + $this->view->assign('filterSettings', $filterSettings); + } + + public function teasergroupAction() + { + $countryUid = (int)$this->settings['countryUid']; + $season = $this->settings['season']; + $regionsPid = (int)$this->settings['regionsStoragePid']; + $regions = $this->regionRepository->getTeasersByCountryAndSeason($countryUid, $season, $regionsPid); + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('teasers', $regions); + } + +} diff --git a/packages/ep_products/Classes/Controller/ResellerController.php b/packages/ep_products/Classes/Controller/ResellerController.php new file mode 100644 index 0000000..24e2f19 --- /dev/null +++ b/packages/ep_products/Classes/Controller/ResellerController.php @@ -0,0 +1,99 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Model\Reseller; +use EP\EpProducts\Service\MyEpUrlResolverService; +use EP\EpProducts\Service\ResellerDataService; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class ResellerController extends ActionController +{ + /** + * @var ResellerDataService + */ + protected $resellerDataService; + + /** + * @var MyEpUrlResolverService + */ + protected $myEpUrlResolverService; + + /** + * @param ResellerDataService $resellerDataService + * @param MyEpUrlResolverService $myEpUrlResolverService + */ + public function __construct( + ResellerDataService $resellerDataService, + MyEpUrlResolverService $myEpUrlResolverService + ) { + $this->resellerDataService = $resellerDataService; + $this->myEpUrlResolverService = $myEpUrlResolverService; + } + + /** + * @param Reseller $reseller + */ + public function listAction(Reseller $reseller = null) + { + $products = []; + + if ($reseller !== null) { + $products = $reseller->getProducts(); + } + + $this->view->assign('reseller', $reseller); + $this->view->assign('products', $products); + } + + /** + * @param Reseller $reseller + * @param Product $product + * @throws \Doctrine\DBAL\DBALException + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException + */ + public function copypasteAction(Reseller $reseller, Product $product) + { + if (!$reseller->getProducts()->contains($product)) { + $this->redirect( + 'list', + null, + null, + [ 'reseller' => $reseller ] + ); + } + + $myEpUrl = $this->myEpUrlResolverService->resolve(); + $productData = $this->resellerDataService->preprocessSingle($product, $reseller, $myEpUrl); + + $this->view->assign('reseller', $reseller); + $this->view->assign('productData', $productData); + } +} diff --git a/packages/ep_products/Classes/Controller/SearchController.php b/packages/ep_products/Classes/Controller/SearchController.php new file mode 100644 index 0000000..dc3d552 --- /dev/null +++ b/packages/ep_products/Classes/Controller/SearchController.php @@ -0,0 +1,168 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\FilterSettingsEncoder; +use EP\EpProducts\Service\SearchResultService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use EP\EpProducts\Utility\DateUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class SearchController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var SearchResultService + */ + private $searchResultService; + + /** + * @param FilterService $filterService + */ + public function __construct(FilterService $filterService, SearchResultService $searchResultService) + { + $this->filterService = $filterService; + $this->searchResultService = $searchResultService; + } + + public function initializeProcessSearchAction() + { + $this->processSearchParamsArgument(); + } + + /** + * @param array $searchParams + */ + public function processSearchAction(array $searchParams) + { + $filterSettings = $this->filterService->getFilterSettingsFromSearchParams($searchParams); + + $redirectUri = $this->uriBuilder + ->reset() + ->setCreateAbsoluteUri(true) + ->uriFor( + 'searchresult', + ['filterSettings' => $filterSettings], + null, + null, + 'searchresult' + ); + + $this->redirectToUri($redirectUri); + } + + public function initializeSearchresultAction() + { + // Process encoded filtersettings from static search result. + // Explicit filter settings win: they carry the visitor's current selection, + // whereas a stale 'f' riding along on a link would silently replace it. + $encodedFilterSettings = GeneralUtility::_GET('f'); + if ($encodedFilterSettings && !$this->request->hasArgument('filterSettings')) { + $filterSettingsEncoder = new FilterSettingsEncoder(); + $filterSettings = $filterSettingsEncoder->decode($encodedFilterSettings); + if (count($filterSettings) > 0) { + $this->request->setArgument('filterSettings', $filterSettings); + } + } + $forcedConceptUids = GeneralUtility::intExplode(',', $this->settings['forcedConceptUids'], true); + $this->processFilterSettingsArgument($forcedConceptUids); + } + + /** + * @param array $filterSettings + */ + public function searchresultAction(array $filterSettings) + { + $dateSelected = false; + $organic = true; + $forcedConceptUids = GeneralUtility::intExplode(',', $this->settings['forcedConceptUids'], true); + $excludedConceptUids = GeneralUtility::intExplode(',', $this->settings['excludedConceptUids']); + + if (!empty($filterSettings['dateFrom']) || !empty($filterSettings['dateTo'])) { + $dateSelected = true; + } + + $searchResult = $this + ->searchResultService + ->getResult($filterSettings, $excludedConceptUids); + + // No results? First retry without date constraint if any + if ((!empty($filterSettings['dateFrom']) || !empty($filterSettings['dateTo'])) && 0 === $searchResult->getTotal()) { + $filterSettings['dateFrom'] = null; + $filterSettings['dateTo'] = null; + $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); + $organic = false; + } + + // Still no results? Remove all constraints and search again with defaults + if (0 === $searchResult->getTotal()) { + $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids); + $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); + $organic = false; + } + + $filterOptions = $this->filterService->collectFilterOptionsFromSearchResult($searchResult); + + $referringPageUri = $this + ->uriBuilder + ->reset() + ->setTargetPageUid($this->settings['defaultSearchPageUid']) + ->setCreateAbsoluteUri(true) + ->uriFor( + 'searchresult', + ['filterSettings' => $filterSettings], + 'Search', + 'epproducts', + 'searchresult' + ) + ; + + $this->view->assignMultiple([ + 'filterSettings' => $filterSettings, + 'filterOptions' => $filterOptions, + 'searchResult' => $searchResult->getData(), + 'referringPageUri' => $referringPageUri, + 'stimulusController' => 'search', + 'meta' => [ + 'organic' => $organic, + 'total' => $searchResult->getTotal(), + 'dateRange' => DateUtility::formatDateRange($filterSettings['dateFrom'], $filterSettings['dateTo']), + 'dateSelected' => $dateSelected, + ], + ]); + } +} diff --git a/packages/ep_products/Classes/Controller/SearchbarController.php b/packages/ep_products/Classes/Controller/SearchbarController.php new file mode 100644 index 0000000..2f9b6d1 --- /dev/null +++ b/packages/ep_products/Classes/Controller/SearchbarController.php @@ -0,0 +1,87 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\SearchResultService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class SearchbarController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var FilterService + */ + protected $filterService; + + /** + * @var SearchResultService + */ + protected $searchResultService; + + /** + * @param FilterService $filterService + * @param SearchResultService $searchResultService + */ + public function __construct(FilterService $filterService, SearchResultService $searchResultService) + { + $this->filterService = $filterService; + $this->searchResultService = $searchResultService; + } + + public function initializeAction() + { + $this->processSearchParamsArgument(); + } + + /** + * @param array $searchParams + */ + public function indexAction(array $searchParams) + { + $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); + $filterSettings = $this->filterService->getFilterSettingsFromSearchParams($searchParams, $forcedConceptUids); + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']); + + $searchResult = $this + ->searchResultService + ->getResult($filterSettings, $excludedConceptUids); + + $filterOptions = $this->filterService->collectFilterOptionsFromSearchResult($searchResult); + + $this->view->assignMultiple([ + 'filterOptions' => $filterOptions, + 'searchParams' => $searchParams, + 'collapsed' => (bool)$this->settings['collapsed'], + ]); + } +} diff --git a/packages/ep_products/Classes/Controller/SnowreportController.php b/packages/ep_products/Classes/Controller/SnowreportController.php new file mode 100644 index 0000000..10d4a0c --- /dev/null +++ b/packages/ep_products/Classes/Controller/SnowreportController.php @@ -0,0 +1,59 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Repository\SnowreportRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class SnowreportController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\SnowreportRepository + */ + protected $snowreportRepository; + + /** + * @param SnowreportRepository $snowreportRepository + */ + public function __construct(SnowreportRepository $snowreportRepository) + { + $this->snowreportRepository = $snowreportRepository; + } + + public function listAction() + { + $regionUids = GeneralUtility::trimExplode(',', $this->settings['regionUids'], true); + $list = $this->snowreportRepository->getSnowreportsForRegions($regionUids); + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('list', $list); + } + +} diff --git a/packages/ep_products/Classes/Controller/TeamController.php b/packages/ep_products/Classes/Controller/TeamController.php new file mode 100644 index 0000000..0d08fc4 --- /dev/null +++ b/packages/ep_products/Classes/Controller/TeamController.php @@ -0,0 +1,96 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Teammember; +use EP\EpProducts\Domain\Repository\TeammemberRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class TeamController extends ActionController +{ + /** + * @var \EP\EpProducts\Domain\Repository\TeammemberRepository + */ + protected $teammemberRepository; + + /** + * @param TeammemberRepository $teammemberRepository + */ + public function __construct(TeammemberRepository $teammemberRepository) + { + $this->teammemberRepository = $teammemberRepository; + } + + public function listAction() + { + if (isset($this->settings['teamCategories']) && count(GeneralUtility::trimExplode(',', $this->settings['teamCategories'], true)) > 0) { + $members = $this->teammemberRepository->findByCategories(GeneralUtility::trimExplode(',', $this->settings['teamCategories'], true)); + } else { + $members = $this->teammemberRepository->findAll(); + } + + $this->view->assign('members', $members); + } + + /** + * @param Teammember $member + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException + */ + public function showAction(Teammember $member = null) + { + if ($member === null) { + $this->redirect('list', null, null, null, null, 0, 302); + } + + $this->view->assign('member', $member); + $this->view->assign('data', $this->configurationManager->getContentObject()->data); + $this->view->assign('header', $this->coalesceHeader($member)); + } + + /** + * @param Teammember $member + * + * @return string + */ + public function coalesceHeader(Teammember $member) + { + $data = $this->configurationManager->getContentObject()->data; + if (isset($data['header']) && $data['header'] !== '') { + return $data['header']; + } + + $firstCategory = current($member->getCategories()->toArray()); + if ($firstCategory !== null && $firstCategory !== false) { + return $firstCategory->getTitle(); + } + + return ''; + } +} diff --git a/packages/ep_products/Classes/Controller/TravelinfoController.php b/packages/ep_products/Classes/Controller/TravelinfoController.php new file mode 100644 index 0000000..9e880bc --- /dev/null +++ b/packages/ep_products/Classes/Controller/TravelinfoController.php @@ -0,0 +1,105 @@ +apiClient = $apiClient; + $this->travelinfoRepository = $travelinfoRepository; + } + + public function travelCodeAction(string $travelCode) + { + // find specific travel info by code including travel date first + $travelInfo = $this->travelinfoRepository->findOneByTravelCode($travelCode); + + // find general travel info by code without travel date as fallback + if (null === $travelInfo) { + $travelCodeBase = substr($travelCode, 0, -6); + $travelInfo = $this->travelinfoRepository->findOneByTravelCode($travelCodeBase); + } + + // return 404 in case no travel info is available as last resort + if (null === $travelInfo) { + $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction( + $GLOBALS['TYPO3_REQUEST'], + 'Reiseinformationen nicht gefunden' + ); + throw new ImmediateResponseException($response, 1731071856); + } + + $this->handleRequest($travelCode, $travelInfo); + } + + public function travelInfoAction(Travelinfo $travelInfo) + { + $travelCode = $travelInfo->getTravelCode(); + + $this->handleRequest($travelCode, $travelInfo); + } + + private function handleRequest(string $travelCode, Travelinfo $travelInfo) + { + + // fetch travel data via API call + try { + $travelData = $this->apiClient->getTravel($travelCode); + } catch (ApiException $e) { + $travelData = []; + } + + try { + $pickupsPlanning = $this->apiClient->getPickupsPlanning($travelCode); + } catch (ApiException $e) { + $pickupsPlanning = []; + } + + // extract courses from additional services + $courses = array_filter($travelData['additionalServices'] ?? [], function ($service) { + return 'KUR' === $service['subType']; + }); + + // extract board services from included services + $includedServicesBoard = $travelData['selectionGroups'][self::SELECTION_ID_BOARD] ?? []; + $board = $includedServicesBoard['selections'] ?? []; + + // extract bus departure time if applicable + $departureDayTime = null; + $service = array_filter($travelData['transportationServices'] ?? [], function ($service) { + return 'BUS' === $service['subType'] && 'RUECK' === $service['direction']; + }); + if ($serviceDetail = reset($service)) { + $departureDayTime = $serviceDetail['dayTime']; + } + + $this->view->assign('travelInfo', $travelInfo); + $this->view->assign('travelData', $travelData); + $this->view->assign('pickupsPlanning', $pickupsPlanning); + $this->view->assign('courses', $courses); + $this->view->assign('board', $board); + $this->view->assign('departureDayTime', $departureDayTime); + } +} diff --git a/packages/ep_products/Classes/Controller/WatchlistController.php b/packages/ep_products/Classes/Controller/WatchlistController.php new file mode 100644 index 0000000..74f46ac --- /dev/null +++ b/packages/ep_products/Classes/Controller/WatchlistController.php @@ -0,0 +1,42 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class WatchlistController extends ActionController +{ + + public function listAction() + { + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('referringPageUid', $GLOBALS['TSFE']->id); + } + +} diff --git a/packages/ep_products/Classes/CsvFormatter/FacebookCatalogCsvFormatter.php b/packages/ep_products/Classes/CsvFormatter/FacebookCatalogCsvFormatter.php new file mode 100644 index 0000000..2b86448 --- /dev/null +++ b/packages/ep_products/Classes/CsvFormatter/FacebookCatalogCsvFormatter.php @@ -0,0 +1,66 @@ + 0 ? 'in stock' : 'out of stock', + 'new', + $description, + $product['images']['image'][0], + $product['link'], + $product['name'], + sprintf('%.2f EUR', $minPrice), + 'brand-epreisen' + ]; + } + + return $csv; + } +} diff --git a/packages/ep_products/Classes/CsvFormatter/GoogleBusinessFeedCsvFormatter.php b/packages/ep_products/Classes/CsvFormatter/GoogleBusinessFeedCsvFormatter.php new file mode 100644 index 0000000..9a8e17c --- /dev/null +++ b/packages/ep_products/Classes/CsvFormatter/GoogleBusinessFeedCsvFormatter.php @@ -0,0 +1,58 @@ + 0) { + $csv[] = [ + $product['@id'], + $product['@id'], + $product['name'], + $product['link'], + $product['images']['image'][0], + sprintf('%.2f EUR', $minPrice), + ]; + } + } + + return $csv; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/AbstractDestination.php b/packages/ep_products/Classes/Domain/Model/AbstractDestination.php new file mode 100644 index 0000000..35413e0 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/AbstractDestination.php @@ -0,0 +1,420 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class AbstractDestination extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + const SEASON_WINTER = 'w'; + const SEASON_SUMMER = 's'; + + /** + * @var string + */ + protected $detailPage; + + /** + * @var string + */ + protected $season = self::SEASON_WINTER; + + /** + * @var float + */ + protected $latitude = 0.0; + + /** + * @var float + */ + protected $longitude = 0.0; + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $nameInternal = ''; + + /** + * @var string + */ + protected $keywords = ''; + + /** + * @var string + */ + protected $headline = ''; + + /** + * @var string + */ + protected $teaser = ''; + + /** + * @var string + */ + protected $headerTitle = ''; + + /** + * @var string + */ + protected $headerSubtitle = ''; + + /** + * @var string + */ + protected $description = ''; + + /** + * @var string + */ + protected $descriptionExtended = ''; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $images; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $teaserImages; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $headerImages; + + public function __construct() + { + $this->initStorageObjects(); + } + + protected function initStorageObjects() + { + $this->images = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->teaserImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->headerImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return string + */ + public function getDetailPage() + { + return $this->detailPage; + } + + /** + * @param string $detailPage + */ + public function setDetailPage($detailPage) + { + $this->detailPage = $detailPage; + } + + /** + * @return string + */ + public function getSeason() + { + return $this->season; + } + + /** + * @param string $season + */ + public function setSeason($season) + { + $this->season = $season; + } + + public function isWinter(): bool + { + return self::SEASON_WINTER === $this->season; + } + + public function isSummer(): bool + { + return self::SEASON_SUMMER === $this->season; + } + + /** + * @return float $latitude + */ + public function getLatitude() + { + return $this->latitude; + } + + /** + * @param float $latitude + */ + public function setLatitude($latitude) + { + $this->latitude = $latitude; + } + + /** + * @return float $longitude + */ + public function getLongitude() + { + return $this->longitude; + } + + /** + * @param float $longitude + */ + public function setLongitude($longitude) + { + $this->longitude = $longitude; + } + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getNameInternal() + { + return $this->nameInternal; + } + + /** + * @param string $nameInternal + */ + public function setNameInternal($nameInternal) + { + $this->nameInternal = $nameInternal; + } + + /** + * @return string + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * @param string $keywords + */ + public function setKeywords($keywords) + { + $this->keywords = $keywords; + } + + /** + * @return string + */ + public function getHeadline() + { + return $this->headline; + } + + /** + * @param string $headline + */ + public function setHeadline($headline) + { + $this->headline = $headline; + } + + /** + * @return string + */ + public function getTeaser() + { + return $this->teaser; + } + + /** + * @param string $teaser + */ + public function setTeaser($teaser) + { + $this->teaser = $teaser; + } + + /** + * @return string + */ + public function getHeaderTitle() + { + return $this->headerTitle; + } + + /** + * @param string $headerTitle + */ + public function setHeaderTitle($headerTitle) + { + $this->headerTitle = $headerTitle; + } + + /** + * @return string + */ + public function getHeaderSubtitle() + { + return $this->headerSubtitle; + } + + /** + * @param string $headerSubtitle + */ + public function setHeaderSubtitle($headerSubtitle) + { + $this->headerSubtitle = $headerSubtitle; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return string + */ + public function getDescriptionExtended() + { + return $this->descriptionExtended; + } + + /** + * @param string $descriptionExtended + */ + public function setDescriptionExtended($descriptionExtended) + { + $this->descriptionExtended = $descriptionExtended; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $images + */ + public function getImages() + { + return $this->images; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $images + */ + public function setImages(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $images) + { + $this->images = $images; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImages() + { + return $this->teaserImages; + } + + /** + * @return null|\TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImage() + { + if ($this->teaserImages->count() === 0) { + return null; + } + + return $this->teaserImages->toArray()[0]; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $teaserImages + */ + public function setTeaserImages($teaserImages) + { + $this->teaserImages = $teaserImages; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImages() + { + return $this->headerImages; + } + + /** + * @return null|\TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getHeaderImage() + { + $headerImages = $this->getHeaderImages(); + + if ($headerImages->count() === 0) { + return null; + } + + return $headerImages->toArray()[0]; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $headerImages + */ + public function setHeaderImages($headerImages) + { + $this->headerImages = $headerImages; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Badge.php b/packages/ep_products/Classes/Domain/Model/Badge.php new file mode 100644 index 0000000..65cc14c --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Badge.php @@ -0,0 +1,97 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Badge extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $title; + + /** + * @var string + */ + protected $subtitle; + + /** + * @var string + */ + protected $link; + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @return string + */ + public function getSubtitle() + { + return $this->subtitle; + } + + /** + * @param string $subtitle + */ + public function setSubtitle($subtitle) + { + $this->subtitle = $subtitle; + } + + /** + * @return string + */ + public function getLink() + { + return $this->link; + } + + /** + * @param string $link + */ + public function setLink($link) + { + $this->link = $link; + } + +} + diff --git a/packages/ep_products/Classes/Domain/Model/City.php b/packages/ep_products/Classes/Domain/Model/City.php new file mode 100644 index 0000000..f8c3010 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/City.php @@ -0,0 +1,199 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class City extends AbstractDestination implements TeaserInterface +{ + + /** + * @var bool + */ + protected $excludeFromTeasergroups = false; + + /** + * @var string + */ + protected $party = ''; + + /** + * @var string + */ + protected $leisure = ''; + + /** + * @var \EP\EpProducts\Domain\Model\Country + */ + protected $country; + + /** + * @var \EP\EpProducts\Domain\Model\Region + */ + protected $region; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> + */ + protected $facts; + + /** + * @var \EP\EpProducts\Domain\Model\Officetip + */ + protected $officetip; + + protected function initStorageObjects() + { + parent::initStorageObjects(); + $this->facts = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return bool + */ + public function isExcludeFromTeasergroups() + { + return $this->excludeFromTeasergroups; + } + + /** + * @param bool $excludeFromTeasergroups + */ + public function setExcludeFromTeasergroups($excludeFromTeasergroups) + { + $this->excludeFromTeasergroups = (bool) $excludeFromTeasergroups; + } + + /** + * @return string + */ + public function getParty() { + return $this->party; + } + + /** + * @param string $party + */ + public function setParty($party) { + $this->party = $party; + } + + /** + * @return string + */ + public function getLeisure() { + return $this->leisure; + } + + /** + * @param string $leisure + */ + public function setLeisure($leisure) { + $this->leisure = $leisure; + } + + /** + * Returns all header images with fallback to parent + * region in case none are assigned. + * + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImages() + { + $headerImages = $this->headerImages; + + if ($headerImages->count() === 0 && null !== $this->getRegion()) { + $headerImages = $this->getRegion()->getHeaderImages(); + } + + return $headerImages; + } + + /** + * @return \EP\EpProducts\Domain\Model\Country $country + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param \EP\EpProducts\Domain\Model\Country $country + */ + public function setCountry(\EP\EpProducts\Domain\Model\Country $country) + { + $this->country = $country; + } + + /** + * @return \EP\EpProducts\Domain\Model\Region $region + */ + public function getRegion() + { + return $this->region; + } + + /** + * @param \EP\EpProducts\Domain\Model\Region $region + */ + public function setRegion(\EP\EpProducts\Domain\Model\Region $region) + { + $this->region = $region; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function getFacts() + { + return $this->facts; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function setFacts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $facts) + { + $this->facts = $facts; + } + + /** + * @return Officetip + */ + public function getOfficetip() + { + return $this->officetip; + } + + /** + * @param Officetip $officetip + */ + public function setOfficetip($officetip) + { + $this->officetip = $officetip; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Concept.php b/packages/ep_products/Classes/Domain/Model/Concept.php new file mode 100644 index 0000000..6878b57 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Concept.php @@ -0,0 +1,434 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Concept extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements TeaserInterface +{ + + /** + * @var int + */ + protected $busProId = 0; + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $code = ''; + + /** + * @var string + */ + protected $headline = ''; + + /** + * @var string + */ + protected $headerTitle = ''; + + /** + * @var string + */ + protected $headerSubtitle = ''; + + /** + * @var string + */ + protected $teaser = ''; + + /** + * @var string + */ + protected $popupTitle = 'Hinweis'; + + /** + * @var string + */ + protected $popupText = ''; + + /** + * @var string + */ + protected $popupButtonLabel = 'Ok'; + + /** + * @var string + */ + protected $description = ''; + + /** + * @var string + */ + protected $descriptionExtended = ''; + + /** + * @var string + */ + protected $detailPage; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $teaserImages; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $headerImages; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $images; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Product> + */ + protected $products; + + /** + * __construct + */ + public function __construct() + { + $this->initStorageObjects(); + } + + /** + */ + protected function initStorageObjects() + { + $this->products = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->teaserImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->headerImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->images = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return int + */ + public function getBusProId() { + return $this->busProId; + } + + /** + * @param int $busProId + */ + public function setBusProId($busProId) { + $this->busProId = $busProId; + } + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @return string + */ + public function getTeaser() + { + return $this->teaser; + } + + /** + * @param string $teaser + */ + public function setTeaser($teaser) + { + $this->teaser = $teaser; + } + + public function getPopupText() + { + return $this->popupText; + } + + public function setPopupText($popupText) + { + $this->popupText = $popupText; + + return $this; + } + + public function getPopupTitle() + { + return $this->popupTitle; + } + + public function setPopupTitle($popupTitle) + { + $this->popupTitle = $popupTitle; + + return $this; + } + + public function getPopupButtonLabel() + { + return $this->popupButtonLabel; + } + + public function setPopupButtonLabel($popupButtonLabel) + { + $this->popupButtonLabel = $popupButtonLabel; + + return $this; + } + + /** + * @return string + */ + public function getHeadline() + { + return $this->headline; + } + + /** + * @param string $headline + */ + public function setHeadline($headline) + { + $this->headline = $headline; + } + + /** + * @return string + */ + public function getHeaderTitle() + { + return $this->headerTitle; + } + + /** + * @param string $headerTitle + */ + public function setHeaderTitle($headerTitle) + { + $this->headerTitle = $headerTitle; + } + + /** + * @return string + */ + public function getHeaderSubtitle() + { + return $this->headerSubtitle; + } + + /** + * @param string $headerSubtitle + */ + public function setHeaderSubtitle($headerSubtitle) + { + $this->headerSubtitle = $headerSubtitle; + } + + /** + * @return string $description + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return string + */ + public function getDescriptionExtended() + { + return $this->descriptionExtended; + } + + /** + * @param string $descriptionExtended + */ + public function setDescriptionExtended($descriptionExtended) + { + $this->descriptionExtended = $descriptionExtended; + } + + /** + * @return string $detailPage + */ + public function getDetailPage() + { + return $this->detailPage; + } + + /** + * @param string $detailPage + */ + public function setDetailPage($detailPage) + { + $this->detailPage = $detailPage; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImages() + { + return $this->teaserImages; + } + + /** + * @return null|\TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImage() + { + if ($this->teaserImages->count() === 0) { + return null; + } + + return $this->teaserImages->toArray()[0]; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $teaserImages + */ + public function setTeaserImages($teaserImages) + { + $this->teaserImages = $teaserImages; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImages() + { + return $this->headerImages; + } + + /** + * @return null|\TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImage() + { + if ($this->headerImages->count() === 0) { + return null; + } + + return $this->headerImages->toArray()[0]; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $headerImages + */ + public function setHeaderImages($headerImages) + { + $this->headerImages = $headerImages; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getImages() + { + return $this->images; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $images + */ + public function setImages($images) + { + $this->images = $images; + } + + /** + * @param \EP\EpProducts\Domain\Model\Product $product + */ + public function addProduct(\EP\EpProducts\Domain\Model\Product $product) + { + $this->products->attach($product); + } + + /** + * @param \EP\EpProducts\Domain\Model\Product $product + */ + public function removeProduct(\EP\EpProducts\Domain\Model\Product $product) + { + $this->products->detach($product); + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Product> + */ + public function getProducts() + { + return $this->products; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Product> $products + */ + public function setProducts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $products) + { + $this->products = $products; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Contingent.php b/packages/ep_products/Classes/Domain/Model/Contingent.php new file mode 100644 index 0000000..a6a681a --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Contingent.php @@ -0,0 +1,353 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use CalendR\Event\EventInterface; +use CalendR\Period\PeriodInterface; +use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; + +class Contingent extends AbstractEntity implements EventInterface +{ + const STATUS_OK = 0; + const STATUS_BLOCKED = 1; + const STATUS_ONREQUEST = 2; + + /** + * @var \DateTime + */ + protected $date; + + /** + * @var \EP\EpProducts\Domain\Model\Hotel + */ + protected $hotel; + + /** + * @var string + */ + protected $roomCode; + + /** + * @var string + */ + protected $roomLabel; + + /** + * @var int + */ + protected $pax; + + /** + * @var int + */ + protected $available; + + /** + * @var int + */ + protected $status; + + /** + * @var int + */ + protected $minPrice; + + /** + * @var int + */ + protected $minNights; + + /** + * @var int + */ + protected $additionalNightMinPrice; + + /** + * @var int + */ + protected $additionalNightMinNights; + + /** + * @param array $data + * @return Contingent + */ + public static function fromArray(array $data) + { + $event = new self; + + $event->setPax($data['pax']); + $event->setAvailable($data['available']); + $event->setDate(new \DateTime($data['date'])); + $event->setStatus($data['status']); + + if ($data['hotel'] instanceof Hotel) { + $event->setHotel($data['hotel']); + } + + return $event; + } + + /** + * @return \DateTime + */ + public function getDate() + { + return $this->date; + } + + /** + * @param \DateTime $date + */ + public function setDate(\DateTime $date) + { + $this->date = $date; + } + + /** + * @return Hotel + */ + public function getHotel() + { + return $this->hotel; + } + + /** + * @param Hotel $hotel + */ + public function setHotel($hotel) + { + $this->hotel = $hotel; + } + + /** + * @return string + */ + public function getRoomCode() + { + return $this->roomCode; + } + + /** + * @param string $roomCode + */ + public function setRoomCode($roomCode) + { + $this->roomCode = $roomCode; + } + + /** + * @return string + */ + public function getRoomLabel() + { + return $this->roomLabel; + } + + /** + * @param string $roomLabel + */ + public function setRoomLabel($roomLabel) + { + $this->roomLabel = $roomLabel; + } + + /** + * @return int + */ + public function getPax() + { + return $this->pax; + } + + /** + * @param int $pax + */ + public function setPax($pax) + { + $this->pax = $pax; + } + + /** + * @return int + */ + public function getAvailable() + { + return $this->available; + } + + /** + * @param int $available + */ + public function setAvailable($available) + { + $this->available = $available; + } + + /** + * @return int + */ + public function getPercentageAvailable() + { + if ($this->pax === 0) { + return 0; + } + + return round($this->available / $this->pax * 100, 2); + } + + /** + * @return int + */ + public function getStatus() + { + return $this->status; + } + + /** + * @param int $status + */ + public function setStatus($status) + { + $this->status = $status; + } + + /** + * @return int + */ + public function getMinPrice() + { + return $this->minPrice; + } + + /** + * @param int $minPrice + */ + public function setMinPrice($minPrice) + { + $this->minPrice = $minPrice; + } + + /** + * @return int + */ + public function getMinNights() + { + return $this->minNights; + } + + /** + * @param int $minNights + */ + public function setMinNights($minNights) + { + $this->minNights = $minNights; + } + + /** + * @return int + */ + public function getAdditionalNightMinPrice() + { + return $this->additionalNightMinPrice; + } + + /** + * @param int $additionalNightMinPrice + */ + public function setAdditionalNightMinPrice($additionalNightMinPrice) + { + $this->additionalNightMinPrice = $additionalNightMinPrice; + } + + /** + * @return int + */ + public function getAdditionalNightMinNights() + { + return $this->additionalNightMinNights; + } + + /** + * @param int $additionalNightMinNights + */ + public function setAdditionalNightMinNights($additionalNightMinNights) + { + $this->additionalNightMinNights = $additionalNightMinNights; + } + + /** + * @return \DateTime + */ + public function getBegin() + { + return $this->date; + } + + /** + * @return \DateTime + */ + public function getEnd() + { + return $this->date; + } + + /** + * @param \DateTime $datetime + * @return bool + */ + public function contains(\DateTime $datetime) + { + return $datetime >= $this->getBegin() && $datetime < $this->getEnd(); + } + + /** + * @param PeriodInterface $period + * @return bool + */ + public function containsPeriod(PeriodInterface $period) + { + return $this->contains($period->getBegin()) && $this->contains($period->getEnd()); + } + + /** + * @param PeriodInterface $period + * @return bool + */ + public function isDuring(PeriodInterface $period) + { + return $this->getBegin() >= $period->getBegin() && $this->getEnd() < $period->getEnd(); + } + + /** + * @return int + */ + public function getOccupancy() + { + return $this->getPax() - $this->getAvailable(); + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Country.php b/packages/ep_products/Classes/Domain/Model/Country.php new file mode 100644 index 0000000..043dcaf --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Country.php @@ -0,0 +1,158 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Country extends AbstractDestination implements TeaserInterface +{ + + /** + * @var string + */ + protected $code = ''; + + /** + * @var string + */ + protected $descriptionExtended = ''; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> + */ + protected $facts; + + /** + * @var \EP\EpProducts\Domain\Model\Officetip + */ + protected $officetip; + + /** + * @var \EP\EpProducts\Domain\Model\Snowreport + */ + protected $snowreport; + + protected function initStorageObjects() + { + parent::initStorageObjects(); + $this->facts = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return string + */ + public function getCode() { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) { + $this->code = $code; + } + + /** + * @return string + */ + public function getDescriptionExtended() + { + return $this->descriptionExtended; + } + + /** + * @param string $descriptionExtended + */ + public function setDescriptionExtended($descriptionExtended) + { + $this->descriptionExtended = $descriptionExtended; + } + + /** + * @param \EP\EpProducts\Domain\Model\Fact $fact + */ + public function addFact(\EP\EpProducts\Domain\Model\Fact $fact) + { + $this->facts->attach($fact); + } + + /** + * @param \EP\EpProducts\Domain\Model\Fact $factToRemove + */ + public function removeFact(\EP\EpProducts\Domain\Model\Fact $factToRemove) + { + $this->facts->detach($factToRemove); + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> + */ + public function getFacts() + { + return $this->facts; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function setFacts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $facts) + { + $this->facts = $facts; + } + + /** + * @return Officetip + */ + public function getOfficetip() + { + return $this->officetip; + } + + /** + * @param Officetip $officetip + */ + public function setOfficetip($officetip) + { + $this->officetip = $officetip; + } + + /** + * @return Snowreport + */ + public function getSnowreport() + { + return $this->snowreport; + } + + /** + * @param Snowreport $snowreport + */ + public function setSnowreport($snowreport) + { + $this->snowreport = $snowreport; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Date.php b/packages/ep_products/Classes/Domain/Model/Date.php new file mode 100644 index 0000000..c7b38eb --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Date.php @@ -0,0 +1,639 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Date extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + /** + * @var Product + */ + protected $product; + + /** + * @var Hotel + */ + protected $hotel; + + /** + * @var Country + */ + protected $country; + + /** + * @var Region + */ + protected $region; + + /** + * @var City + */ + protected $city; + + /** + * @var int + */ + protected $busProId = 0; + + /** + * @var int + */ + protected $hotelBusProId = 0; + + /** + * @var string + */ + protected $code = ''; + + /** + * @var bool + */ + protected $daytrip = false; + + /** + * @var \DateTime + */ + protected $dateStart; + + /** + * @var \DateTime + */ + protected $dateEnd; + + /** + * @var int + */ + protected $nights = 0; + + /** + * @var string + */ + protected $board = ''; + + /** + * @var string + */ + protected $boardCode = ''; + + /** + * @var int + */ + protected $boardBusProId = 0; + + /** + * @var bool + */ + protected $busIncluded = false; + + /** + * @var bool + */ + protected $skipassIncluded = false; + + /** + * @var bool + */ + protected $groupTravel = false; + + /** + * @var bool + */ + protected $busAvailable = false; + + /** + * @var float + */ + protected $busPrice = 0.0; + + /** + * @var string + */ + protected $servicesIncluded = ''; + + /** + * @var string + */ + protected $servicesGeneral = ''; + + /** + * @var string + */ + protected $coursesIncluded = ''; + + /** + * @var string + */ + protected $pickups = ''; + + /** + * @var float + */ + protected $discount = 0.0; + + /** + * @var float + */ + protected $minPrice = 0.0; + + /** + * @var float + */ + protected $pseudoPrice = 0.0; + + /** + * @var int + */ + protected $available = 0; + + /** + * @var int + */ + protected $minPax = 0; + + /** + * @var bool + */ + protected $new = false; + + /** + * @var string + */ + protected $guide = ''; + + public function getProduct() + { + return $this->product; + } + + public function setProduct($product) + { + $this->product = $product; + } + + public function getHotel() + { + return $this->hotel; + } + + public function setHotel($hotel) + { + $this->hotel = $hotel; + } + + public function getCountry() + { + return $this->country; + } + + public function setCountry($country) + { + $this->country = $country; + } + + public function getRegion() + { + return $this->region; + } + + public function setRegion($region) + { + $this->region = $region; + } + + public function getCity() + { + return $this->city; + } + + public function setCity($city) + { + $this->city = $city; + } + + /** + * @return int $bookingId + */ + public function getBusProId() + { + return $this->busProId; + } + + /** + * @param int $busProId + */ + public function setBusProId($busProId) + { + $this->busProId = $busProId; + } + + /** + * @return int + */ + public function getHotelBusProId() + { + return $this->hotelBusProId; + } + + /** + * @param int $hotelBusProId + */ + public function setHotelBusProId($hotelBusProId) + { + $this->hotelBusProId = $hotelBusProId; + } + + /** + * @return string $code + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @return bool + */ + public function isDaytrip() + { + return $this->daytrip; + } + + /** + * @param bool $daytrip + */ + public function setDaytrip($daytrip) + { + $this->daytrip = $daytrip; + } + + /** + * @return \DateTime $dateStart + */ + public function getDateStart() + { + return $this->dateStart; + } + + /** + * @param \DateTime $dateStart + */ + public function setDateStart(\DateTime $dateStart) + { + $this->dateStart = $dateStart; + } + + /** + * @return \DateTime $dateEnd + */ + public function getDateEnd() + { + return $this->dateEnd; + } + + /** + * @param \DateTime $dateEnd + */ + public function setDateEnd(\DateTime $dateEnd) + { + $this->dateEnd = $dateEnd; + } + + /** + * @return int + */ + public function getNights() + { + return $this->nights; + } + + /** + * @param int $nights + */ + public function setNights($nights) + { + $this->nights = $nights; + } + + /** + * @return string + */ + public function getBoard() + { + return $this->board; + } + + /** + * @param string $board + */ + public function setBoard($board) + { + $this->board = $board; + } + + /** + * @return string + */ + public function getBoardCode() + { + return $this->boardCode; + } + + /** + * @param string $boardCode + */ + public function setBoardCode(string $boardCode) + { + $this->boardCode = $boardCode; + } + + /** + * @return int + */ + public function getBoardBusProId() + { + return $this->boardBusProId; + } + + /** + * @param int $boardBusProId + */ + public function setBoardBusProId(int $boardBusProId) + { + $this->boardBusProId = $boardBusProId; + } + + /** + * @return bool $busIncluded + */ + public function getBusIncluded() + { + return $this->busIncluded; + } + + /** + * @param bool $busIncluded + */ + public function setBusIncluded($busIncluded) + { + $this->busIncluded = $busIncluded; + } + + /** + * @return bool + */ + public function getSkipassIncluded() + { + return $this->skipassIncluded; + } + + /** + * @param bool $skipassIncluded + */ + public function setSkipassIncluded($skipassIncluded) + { + $this->skipassIncluded = $skipassIncluded; + } + + /** + * @return bool + */ + public function getGroupTravel() + { + return $this->groupTravel; + } + + /** + * @param bool $groupTravel + */ + public function setGroupTravel($groupTravel) + { + $this->groupTravel = $groupTravel; + } + + /** + * @return bool + */ + public function getBusAvailable() + { + return $this->busAvailable; + } + + /** + * @param bool $busAvailable + */ + public function setBusAvailable($busAvailable) + { + $this->busAvailable = $busAvailable; + } + + /** + * @return float + */ + public function getBusPrice() + { + return $this->busPrice; + } + + /** + * @param float $busPrice + */ + public function setBusPrice($busPrice) + { + $this->busPrice = $busPrice; + } + + /** + * @return array $servicesIncluded + */ + public function getServicesIncluded() + { + return unserialize($this->servicesIncluded); + } + + /** + * @param array $servicesIncluded + */ + public function setServicesIncluded(array $servicesIncluded) + { + $this->servicesIncluded = serialize($servicesIncluded); + } + + /** + * @return array $servicesGeneral + */ + public function getServicesGeneral($token = null) + { + $services = unserialize($this->servicesGeneral); + + if (null !== $token) { + return $services[$token] ?? []; + } + + return $services; + } + + /** + * @param array $servicesGeneral + */ + public function setServicesGeneral(array $servicesGeneral) + { + $this->servicesGeneral = serialize($servicesGeneral); + } + + public function getCoursesIncluded() + { + return unserialize($this->coursesIncluded); + } + + public function setCoursesIncluded($coursesIncluded) + { + $this->coursesIncluded = $coursesIncluded; + } + + public function getActiveCoursesForTravelinfo() + { + return $this->getServicesGeneral('KUR'); + } + + public function getGuide() + { + return unserialize($this->guide); + } + + public function setGuide($guide): Date + { + $this->guide = serialize($guide); + } + + /** + * @return string + */ + public function getPickups() + { + return unserialize($this->pickups); + } + + /** + * @param array $pickups + */ + public function setPickups(array $pickups) + { + $this->pickups = serialize($pickups); + } + + /** + * @return float + */ + public function getDiscount() { + return $this->discount; + } + + /** + * @param float $discount + */ + public function setDiscount($discount) { + $this->discount = $discount; + } + + /** + * @return float + */ + public function getMinPrice() + { + return $this->minPrice; + } + + /** + * @param float $minPrice + */ + public function setMinPrice($minPrice) + { + $this->minPrice = $minPrice; + } + + /** + * @return float + */ + public function getPseudoPrice() + { + return $this->pseudoPrice; + } + + /** + * @param float $pseudoPrice + */ + public function setPseudoPrice($pseudoPrice) + { + $this->pseudoPrice = $pseudoPrice; + } + + /** + * @return int + */ + public function getAvailable() + { + return $this->available; + } + + /** + * @param int $available + */ + public function setAvailable($available) + { + $this->available = $available; + } + + public function getMinPax() + { + return $this->minPax; + } + + public function setMinPax($minPax) + { + $this->minPax = $minPax; + + return $this; + } + + public function isNew() + { + return $this->new; + } + + public function setNew(bool $new) + { + $this->new = $new; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Dto/FilterOptions.php b/packages/ep_products/Classes/Domain/Model/Dto/FilterOptions.php new file mode 100644 index 0000000..1b95aba --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Dto/FilterOptions.php @@ -0,0 +1,347 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class FilterOptions implements \JsonSerializable +{ + /** + * @var array + */ + public static $priceRangeOptions = [ + 1 => [ 1, 200 ], + 2 => [ 200, 300 ], + 3 => [ 300, 400 ], + 4 => [ 400, 500 ], + 5 => [ 500, 600 ], + 6 => [ 600, 999 ], + ]; + + /** + * @var array + */ + protected $priceRanges = []; + + /** + * @var array + */ + protected $destinations = []; + + /** + * @var array + */ + protected $concepts = []; + + /** + * @var array + */ + protected $pax = []; + + /** + * @var array + */ + protected $nights = []; + + /** + * @var array + */ + protected $hotelTypes = []; + + /** + * @var array + */ + protected $boardTypes = []; + + /** + * @var array + */ + protected $roomTypes = []; + + /** + * @var \DateTime + */ + protected $dateFrom; + + /** + * @var \DateTime + */ + protected $dateTo; + + /** + * @var bool + */ + protected $busAvailable; + + /** + * @var int + */ + protected $resultCount; + + /** + * @var string + */ + protected $resultsUri; + + public function __construct() + { + $this->pax = [ + 0 => 'beliebig', + ...array_combine(range(1, 24), range(1, 24)) + ]; + } + + /** + * @return array + */ + public function getPriceRanges() + { + return $this->priceRanges; + } + + /** + * @param array $priceRanges + */ + public function setPriceRanges($priceRanges) + { + $this->priceRanges = $priceRanges; + } + + /** + * @return array + */ + public function getDestinations() + { + return $this->destinations; + } + + /** + * @param array $destinations + */ + public function setDestinations(array $destinations) + { + $this->destinations = $destinations; + } + + /** + * @return array + */ + public function getConcepts() + { + return $this->concepts; + } + + /** + * @param array $concepts + */ + public function setConcepts($concepts) + { + $this->concepts = $concepts; + } + + /** + * @return array + */ + public function getPax() + { + return $this->pax; + } + + /** + * @param array $pax + */ + public function setPax($pax) + { + $this->pax = $pax; + } + + /** + * @return array + */ + public function getNights() + { + return $this->nights; + } + + /** + * @param array $nights + */ + public function setNights($nights) + { + $this->nights = $nights; + } + + /** + * @return array + */ + public function getHotelTypes() + { + return $this->hotelTypes; + } + + /** + * @param array $hotelTypes + */ + public function setHotelTypes($hotelTypes) + { + $this->hotelTypes = $hotelTypes; + } + + /** + * @return array + */ + public function getBoardTypes() + { + return $this->boardTypes; + } + + /** + * @param array $boardTypes + */ + public function setBoardTypes($boardTypes) + { + $this->boardTypes = $boardTypes; + } + + /** + * @return array + */ + public function getRoomTypes() + { + return $this->roomTypes; + } + + /** + * @param array $roomTypes + */ + public function setRoomTypes($roomTypes) + { + $this->roomTypes = $roomTypes; + } + + /** + * @return \DateTime + */ + public function getDateFrom() + { + return $this->dateFrom; + } + + /** + * @param \DateTime $dateFrom + */ + public function setDateFrom($dateFrom) + { + $this->dateFrom = $dateFrom; + } + + /** + * @return \DateTime + */ + public function getDateTo() + { + return $this->dateTo; + } + + /** + * @param \DateTime $dateTo + */ + public function setDateTo($dateTo) + { + $this->dateTo = $dateTo; + } + + /** + * @return bool + */ + public function getBusAvailable() + { + return $this->busAvailable; + } + + /** + * @param bool $busAvailable + */ + public function setBusAvailable($busAvailable) + { + $this->busAvailable = $busAvailable; + } + + /** + * @return int + */ + public function getResultCount(): ?int + { + return $this->resultCount; + } + + /** + * @param int $resultCount + */ + public function setResultCount(int $resultCount) + { + $this->resultCount = $resultCount; + } + + /** + * @return string + */ + public function getResultsUri(): ?string + { + return $this->resultsUri; + } + + /** + * @param string $resultsUri + */ + public function setResultsUri(string $resultsUri) + { + $this->resultsUri = $resultsUri; + } + + /** + * @return array + */ + public function jsonSerialize() + { + return [ + 'priceRanges' => $this->getPriceRanges(), + 'destinations' => $this->getDestinations(), + 'concepts' => $this->getConcepts(), + 'pax' => $this->getPax(), + 'nights' => $this->getNights(), + 'hotelTypes' => $this->getHotelTypes(), + 'boardTypes' => $this->getBoardTypes(), + 'roomTypes' => $this->getRoomTypes(), + 'dateFrom' => ($this->getDateFrom() !== null) ? $this->getDateFrom()->format('Y-m-d') : null, + 'dateTo' => ($this->getDateTo() !== null) ? $this->getDateTo()->format('Y-m-d') : null, + 'busAvailable' => $this->getBusAvailable(), + 'resultCount' => $this->getResultCount(), + 'resultsUri' => $this->getResultsUri(), + ]; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php b/packages/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php new file mode 100644 index 0000000..0a9d3ad --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php @@ -0,0 +1,484 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class GroupsPriceInquiry +{ + const MODE_INQUIRY = 'inquiry'; + const MODE_BOOKING = 'booking'; + + /** + * @var string + */ + protected $mode; + + /** + * @var bool + * @TYPO3\CMS\Extbase\Annotation\Validate("Boolean", options={"is": true}) + */ + protected $confirmation; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $name; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $group; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $street; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $postcode; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $city; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + * @TYPO3\CMS\Extbase\Annotation\Validate("EmailAddress") + */ + protected $email; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $phone; + + /** + * @var string + */ + protected $remarks; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $dateFrom; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $dateTo; + + /** + * @var string + */ + protected $nights; + + /** + * @var string + */ + protected $pax; + + /** + * @var string + */ + protected $children; + + /** + * @var string + */ + protected $minors; + + /** + * @var string + */ + protected $adolescents; + + /** + * @var array + */ + protected $options = []; + + /** + * @var GroupsPriceBoard + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $board; + + /** + * @var array + */ + protected $summary; + + public function __construct() + { + $this->mode = self::MODE_BOOKING; + } + + /** + * @return bool + */ + public function isConfirmation() + { + return $this->confirmation; + } + + /** + * @param bool $confirmation + */ + public function setConfirmation($confirmation) + { + $this->confirmation = $confirmation; + } + + /** + * @return string + */ + public function getMode() + { + return $this->mode; + } + + /** + * @param string $mode + */ + public function setMode($mode) + { + $this->mode = $mode; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getGroup() + { + return $this->group; + } + + /** + * @param string $group + */ + public function setGroup($group) + { + $this->group = $group; + + return $this; + } + + /** + * @return string + */ + public function getStreet() + { + return $this->street; + } + + /** + * @param string $street + */ + public function setStreet($street) + { + $this->street = $street; + + return $this; + } + + /** + * @return string + */ + public function getPostcode() + { + return $this->postcode; + } + + /** + * @param string $postcode + */ + public function setPostcode($postcode) + { + $this->postcode = $postcode; + } + + /** + * @return string + */ + public function getCity() + { + return $this->city; + } + + /** + * @param string $city + */ + public function setCity($city) + { + $this->city = $city; + } + + /** + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * @param string $email + */ + public function setEmail($email) + { + $this->email = $email; + } + + /** + * @return string + */ + public function getPhone() + { + return $this->phone; + } + + /** + * @param string $phone + */ + public function setPhone($phone) + { + $this->phone = $phone; + } + + /** + * @return string + */ + public function getRemarks() + { + return $this->remarks; + } + + /** + * @param string $remarks + */ + public function setRemarks($remarks) + { + $this->remarks = $remarks; + } + + /** + * @return string + */ + public function getDateFrom() + { + return $this->dateFrom; + } + + /** + * @param string $dateFrom + */ + public function setDateFrom($dateFrom) + { + $this->dateFrom = $dateFrom; + } + + /** + * @return string + */ + public function getDateTo() + { + return $this->dateTo; + } + + /** + * @param string $dateTo + */ + public function setDateTo($dateTo) + { + $this->dateTo = $dateTo; + } + + /** + * @return string + */ + public function getNights() + { + return $this->nights; + } + + /** + * @param string $nights + */ + public function setNights($nights) + { + $this->nights = $nights; + } + + /** + * @return string + */ + public function getPax() + { + return $this->pax; + } + + /** + * @param string $pax + */ + public function setPax($pax) + { + $this->pax = $pax; + } + + /** + * @return string + */ + public function getChildren() + { + return $this->children; + } + + /** + * @param string $pax + */ + public function setChildren($children) + { + $this->children = $children; + } + + /** + * @return string + */ + public function getMinors() + { + return $this->minors; + } + + /** + * @param string $minors + */ + public function setMinors($minors) + { + $this->minors = $minors; + } + + /** + * @return string + */ + public function getAdolescents() + { + return $this->adolescents; + } + + /** + * @param string $adolescents + */ + public function setAdolescents($adolescents) + { + $this->adolescents = $adolescents; + } + + /** + * @return int + */ + public function getEffectivePax() + { + return max(30, (int) $this->getPax() - (int) $this->getChildren()); + } + + /** + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * @param array $options + */ + public function setOptions(array $options = []) + { + $this->options = $options; + } + + /** + * @return GroupsPriceBoard + */ + public function getBoard() + { + return $this->board; + } + + /** + * @param GroupsPriceBoard $board + */ + public function setBoard(GroupsPriceBoard $board = null) + { + $this->board = $board; + } + + /** + * @return array + */ + public function getSummary() + { + return $this->summary; + } + + /** + * @param array $summary + */ + public function setSummary(array $summary) + { + $this->summary = $summary; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php b/packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php new file mode 100644 index 0000000..2a976ce --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php @@ -0,0 +1,113 @@ + + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + private array $listIds = []; + + /** + * @TYPO3\CMS\Extbase\Annotation\Validate("Boolean", options={"is": true}) + */ + private bool $consent = false; + + public function getFirstName(): ?string + { + return $this->firstName; + } + + public function setFirstName(?string $firstName): self + { + $this->firstName = $firstName; + + return $this; + } + + public function getLastName(): ?string + { + return $this->lastName; + } + + public function setLastName(?string $lastName): self + { + $this->lastName = $lastName; + + return $this; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(?string $email): self + { + $this->email = $email; + + return $this; + } + + /** + * @return list + */ + public function getListIds(): array + { + return $this->listIds; + } + + /** + * @param list $listIds + */ + public function setListIds(array $listIds): self + { + $this->listIds = $listIds; + + return $this; + } + + public function isConsent(): bool + { + return $this->consent; + } + + public function setConsent(bool $consent): self + { + $this->consent = $consent; + + return $this; + } + + public function jsonSerialize(): array + { + return [ + 'firstName' => $this->firstName, + 'lastName' => $this->lastName, + 'email' => $this->email, + 'listIds' => array_map(function($value) { + return (int) $value; + }, $this->listIds), + ]; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Fact.php b/packages/ep_products/Classes/Domain/Model/Fact.php new file mode 100644 index 0000000..79207cd --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Fact.php @@ -0,0 +1,96 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Fact extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $keywords = ''; + + /** + * @var string + */ + protected $label = ''; + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string $keywords + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * @param string $keywords + */ + public function setKeywords($keywords) + { + $this->keywords = $keywords; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * @param string $label + */ + public function setLabel($label) + { + $this->label = $label; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Faq.php b/packages/ep_products/Classes/Domain/Model/Faq.php new file mode 100644 index 0000000..ab8bd89 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Faq.php @@ -0,0 +1,75 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Faq extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $question = ''; + + /** + * @var string + */ + protected $answer = ''; + + /** + * @return string $question + */ + public function getQuestion() + { + return $this->question; + } + + /** + * @param string $question + */ + public function setQuestion($question) + { + $this->question = $question; + } + + /** + * @return string $answer + */ + public function getAnswer() + { + return $this->answer; + } + + /** + * @param string $answer + */ + public function setAnswer($answer) + { + $this->answer = $answer; + } + +} \ No newline at end of file diff --git a/packages/ep_products/Classes/Domain/Model/GroupsPriceBoard.php b/packages/ep_products/Classes/Domain/Model/GroupsPriceBoard.php new file mode 100644 index 0000000..f05e0bb --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/GroupsPriceBoard.php @@ -0,0 +1,111 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class GroupsPriceBoard extends AbstractEntity implements \JsonSerializable +{ + /** + * @var string + */ + protected $title; + + /** + * @var float + */ + protected $price; + + /** + * @var float + */ + protected $priceChf; + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return float + */ + public function getPrice(): float + { + return $this->price; + } + + /** + * @param float $price + */ + public function setPrice(float $price): void + { + $this->price = $price; + } + + /** + * @return float + */ + public function getPriceChf(): ?float + { + return $this->priceChf; + } + + /** + * @param float $price + */ + public function setPriceChf(float $price): void + { + $this->priceChf = $price; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'uid' => $this->getUid(), + 'title' => $this->getTitle(), + 'price' => [ + 'EUR' => $this->getPrice(), + 'CHF' => $this->getPriceChf(), + ], + ]; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/GroupsPriceConfig.php b/packages/ep_products/Classes/Domain/Model/GroupsPriceConfig.php new file mode 100644 index 0000000..dc8a8d0 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/GroupsPriceConfig.php @@ -0,0 +1,201 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class GroupsPriceConfig extends AbstractEntity implements \JsonSerializable +{ + /** + * @var \DateTime + */ + protected $dateFrom; + + /** + * @var \DateTime + */ + protected $dateTo; + + /** + * @var int + */ + protected $personsIncluded; + + /** + * @var float + */ + protected $price; + + /** + * @var float + */ + protected $priceChf; + + /** + * @var float + */ + protected $priceAdditionalPerson; + + /** + * @var float + */ + protected $priceAdditionalPersonChf; + + /** + * @return \DateTime + */ + public function getDateFrom(): \DateTime + { + return $this->dateFrom; + } + + /** + * @param \DateTime $dateFrom + */ + public function setDateFrom(\DateTime $dateFrom): void + { + $this->dateFrom = $dateFrom; + } + + /** + * @return \DateTime + */ + public function getDateTo(): \DateTime + { + return $this->dateTo; + } + + /** + * @param \DateTime $dateTo + */ + public function setDateTo(\DateTime $dateTo): void + { + $this->dateTo = $dateTo; + } + + /** + * @return int + */ + public function getPersonsIncluded(): int + { + return $this->personsIncluded; + } + + /** + * @param int $personsIncluded + */ + public function setPersonsIncluded(int $personsIncluded): void + { + $this->personsIncluded = $personsIncluded; + } + + /** + * @return float + */ + public function getPrice(): float + { + return $this->price; + } + + /** + * @param float $price + */ + public function setPrice(float $price): void + { + $this->price = $price; + } + + /** + * @return float + */ + public function getPriceChf(): ?float + { + return $this->priceChf; + } + + /** + * @param float $price + */ + public function setPriceChf(float $price): void + { + $this->priceChf = $price; + } + + /** + * @return float + */ + public function getPriceAdditionalPerson(): ?float + { + return $this->priceAdditionalPerson; + } + + /** + * @param float $priceAdditionalPerson + */ + public function setPriceAdditionalPerson(float $priceAdditionalPerson): void + { + $this->priceAdditionalPerson = $priceAdditionalPerson; + } + + /** + * @return float + */ + public function getPriceAdditionalPersonChf(): ?float + { + return $this->priceAdditionalPersonChf; + } + + /** + * @param float $priceAdditionalPerson + */ + public function setPriceAdditionalPersonChf(float $priceAdditionalPerson): void + { + $this->priceAdditionalPersonChf = $priceAdditionalPerson; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'uid' => $this->getUid(), + 'dateFrom' => $this->getDateFrom()->format('Y-m-d'), + 'dateTo' => $this->getDateTo()->format('Y-m-d'), + 'personsIncluded' => $this->getPersonsIncluded(), + 'price' => [ + 'EUR' => $this->getPrice(), + 'CHF' => $this->getPriceChf(), + ], + 'priceAdditionalPerson' => [ + 'EUR' => $this->getPriceAdditionalPerson(), + 'CHF' => $this->getPriceAdditionalPersonChf(), + ], + ]; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/GroupsPriceOption.php b/packages/ep_products/Classes/Domain/Model/GroupsPriceOption.php new file mode 100644 index 0000000..d61da7d --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/GroupsPriceOption.php @@ -0,0 +1,160 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class GroupsPriceOption extends AbstractEntity implements \JsonSerializable +{ + const TYPE_GLOBAL = 1; + const TYPE_PER_PAX = 2; + const TYPE_PER_NIGHT = 3; + const TYPE_PER_PAX_AND_NIGHT = 4; + + /** + * @var string + */ + protected $title; + + /** + * @var float + */ + protected $price; + + /** + * @var float + */ + protected $priceChf; + + /** + * @var int + */ + protected $type; + + /** + * @var bool + */ + protected $ignoreWithBoard = false; + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return float + */ + public function getPrice(): float + { + return $this->price; + } + + /** + * @param float $price + */ + public function setPrice(float $price): void + { + $this->price = $price; + } + + /** + * @return float + */ + public function getPriceChf(): ?float + { + return $this->priceChf; + } + + /** + * @param float $price + */ + public function setPriceChf(float $price): void + { + $this->priceChf = $price; + } + + /** + * @return int + */ + public function getType(): int + { + return $this->type; + } + + /** + * @param int $type + */ + public function setType(int $type): void + { + $this->type = $type; + } + + /** + * @return bool + */ + public function isIgnoreWithBoard(): bool + { + return $this->ignoreWithBoard; + } + + /** + * @param bool $ignoreWithBoard + */ + public function setIgnoreWithBoard(bool $ignoreWithBoard): void + { + $this->ignoreWithBoard = $ignoreWithBoard; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'uid' => $this->getUid(), + 'title' => $this->getTitle(), + 'price' => [ + 'EUR' => $this->getPrice(), + 'CHF' => $this->getPriceChf(), + ], + 'type' => $this->getType(), + 'ignoreWithBoard' => $this->isIgnoreWithBoard(), + ]; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Hotel.php b/packages/ep_products/Classes/Domain/Model/Hotel.php new file mode 100644 index 0000000..097fbe8 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Hotel.php @@ -0,0 +1,1273 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Utility\HotelIconUtility; +use TYPO3\CMS\Extbase\Domain\Model\FileReference; +use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; +use TYPO3\CMS\Extbase\Persistence\ObjectStorage; + +class Hotel extends AbstractEntity implements TeaserInterface +{ + + const CATEGORY_STANDARD = 1; + const CATEGORY_COMFORT = 2; + const CATEGORY_DELUXE = 3; + const CATEGORY_SUPER_DELUXE = 4; + const CATEGORY_BASIC = 5; + + const TYPE_HOLIDAY_FLAT = 1; + const TYPE_SPORTS_CLUB = 2; + const TYPE_APPARTMENT = 3; + const TYPE_GUESTHOUSE = 4; + const TYPE_PENSION = 5; + + /** + * @var array + */ + public static $categoryLabels = [ + self::CATEGORY_BASIC => 'Basic', + self::CATEGORY_STANDARD => 'Standard', + self::CATEGORY_COMFORT => 'Komfort', + self::CATEGORY_DELUXE => 'Deluxe', + self::CATEGORY_SUPER_DELUXE => 'Superdeluxe', + ]; + + /** + * @var array + */ + public static $typeLabels = [ + 0 => 'Unbestimmt', + self::TYPE_HOLIDAY_FLAT => 'Ferienwohnung', + self::TYPE_SPORTS_CLUB => 'Sportclub', + self::TYPE_APPARTMENT => 'Apartment', + self::TYPE_GUESTHOUSE => 'Gästehaus', + self::TYPE_PENSION => 'Pension', + ]; + + /** + * @var bool + */ + protected $hidden = true; + + /** + * @var bool + */ + protected $earlybird = false; + + /** + * @var bool + */ + protected $new = false; + + /** + * @var bool + */ + protected $lowContingent = false; + + /** + * @var bool + */ + protected $showAsTeaser = false; + + /** + * @var string + */ + protected $detailPage; + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $shortName; + + /** + * @var string + */ + protected $keywords = ''; + + /** + * @var string + */ + protected $headline = ''; + + /** + * @var string + */ + protected $teaser = ''; + + /** + * @var string + */ + protected $headerTitle = ''; + + /** + * @var string + */ + protected $headerSubtitle = ''; + + /** + * @var string + */ + protected $description = ''; + + /** + * @var string + */ + protected $features = ''; + + /** + * @var string + */ + protected $roomTypes = ''; + + /** + * @var string + */ + protected $additionalInformation = ''; + + /** + * @var float + */ + protected $latitude = 0.0; + + /** + * @var float + */ + protected $longitude = 0.0; + + /** + * @var string + */ + protected $address = ''; + + /** + * @var ObjectStorage + */ + protected $images; + + /** + * @var ObjectStorage + */ + protected $resellerImages; + + /** + * @var ObjectStorage + */ + protected $teaserImages; + + /** + * @var ObjectStorage + */ + protected $headerImages; + + /** + * @var string + */ + protected $code = ''; + + /** + * @var ObjectStorage + * @TYPO3\CMS\Extbase\Annotation\ORM\Lazy + */ + protected $additionalCodes; + + /** + * @var int + */ + protected $type = 0; + + /** + * @var int + */ + protected $category = self::CATEGORY_STANDARD; + + /** + * @var Country + */ + protected $country; + + /** + * @var Region + */ + protected $region; + + /** + * @var City + */ + protected $city; + + /** + * @var ObjectStorage + */ + protected $teamer; + + /** + * @var ObjectStorage + */ + protected $products; + + /** + * @var ObjectStorage + */ + protected $facts; + + /** + * @var string + */ + protected $externalLink; + + /** + * @var string + */ + protected $pathSegment; + + /** + * @var ObjectStorage + */ + protected $groupsPriceConfigs; + + /** + * @var ObjectStorage + */ + protected $groupsPriceOptions; + + /** + * @var ObjectStorage + */ + protected $groupsPriceBoards; + + /** + * @var int + */ + protected $groupsPriceAdolescentsAge; + + /** + * @var string + */ + protected $groupsPriceTaxLabel; + + /** + * @var \DateTime + */ + protected $mainSeasonFrom; + + /** + * @var \DateTime + */ + protected $mainSeasonTo; + + /** + * @var int + */ + protected $bedsCount = 0; + + /** + * @var int + */ + protected $parkingCount = 0; + + /** + * @var string + */ + protected $altitude = ''; + + /** + * @var string + */ + protected $shower = ''; + + /** + * @var bool + */ + protected $partyroom = false; + + /** + * @var bool + */ + protected $gamesroom = false; + + /** + * @var bool + */ + protected $sauna = false; + + /** + * @var bool + */ + protected $wifi = false; + + /** + * @var string + */ + protected $distanceBus = ''; + + /** + * @var string + */ + protected $distanceLift = ''; + + public function __construct() + { + $this->initStorageObjects(); + } + + protected function initStorageObjects() + { + $this->images = new ObjectStorage(); + $this->resellerImages = new ObjectStorage(); + $this->teaserImages = new ObjectStorage(); + $this->headerImages = new ObjectStorage(); + $this->teamer = new ObjectStorage(); + $this->products = new ObjectStorage(); + $this->facts = new ObjectStorage(); + $this->additionalCodes = new ObjectStorage(); + $this->groupsPriceConfigs = new ObjectStorage(); + $this->groupsPriceOptions = new ObjectStorage(); + $this->groupsPriceBoards = new ObjectStorage(); + } + + /** + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * @param bool $hidden + */ + public function setHidden($hidden) + { + $this->hidden = $hidden; + } + + /** + * @return bool + */ + public function isEarlybird() + { + return $this->earlybird; + } + + /** + * @param bool $earlybird + */ + public function setEarlybird($earlybird) + { + $this->earlybird = $earlybird; + } + + /** + * @return bool + */ + public function isNew() + { + return $this->new; + } + + /** + * @param bool $new + */ + public function setNew($new) + { + $this->new = $new; + } + + /** + * @return bool + */ + public function isLowContingent() + { + return $this->lowContingent; + } + + /** + * @param bool $lowContingent + */ + public function setLowContingent($lowContingent) + { + $this->lowContingent = $lowContingent; + } + + /** + * @return bool + */ + public function isShowAsTeaser() + { + return $this->showAsTeaser; + } + + /** + * @param bool $showAsTeaser + */ + public function setShowAsTeaser($showAsTeaser) + { + $this->showAsTeaser = $showAsTeaser; + } + + /** + * @return string + */ + public function getDetailPage() + { + return $this->detailPage; + } + + /** + * @param string $detailPage + */ + public function setDetailPage($detailPage) + { + $this->detailPage = $detailPage; + } + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getShortName() + { + if (empty($this->shortName)) { + return $this->name; + } + + return $this->shortName; + } + + /** + * @param string $shortName + */ + public function setShortName($shortName) + { + $this->shortName = $shortName; + } + + /** + * @return string + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * @param string $keywords + */ + public function setKeywords($keywords) + { + $this->keywords = $keywords; + } + + /** + * @return string + */ + public function getHeadline() { + return $this->headline; + } + + /** + * @param string $headline + */ + public function setHeadline($headline) { + $this->headline = $headline; + } + + /** + * @return string + */ + public function getTeaser() { + return $this->teaser; + } + + /** + * @param string $teaser + */ + public function setTeaser($teaser) { + $this->teaser = $teaser; + } + + /** + * @return string + */ + public function getHeaderTitle() + { + return $this->headerTitle; + } + + /** + * @param string $headerTitle + */ + public function setHeaderTitle($headerTitle) + { + $this->headerTitle = $headerTitle; + } + + /** + * @return string + */ + public function getHeaderSubtitle() + { + return $this->headerSubtitle; + } + + /** + * @param string $headerSubtitle + */ + public function setHeaderSubtitle($headerSubtitle) + { + $this->headerSubtitle = $headerSubtitle; + } + + /** + * @return string $description + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return string + */ + public function getFeatures() { + return $this->features; + } + + /** + * @param string $features + */ + public function setFeatures($features) { + $this->features = $features; + } + + /** + * @return string + */ + public function getRoomTypes() { + return $this->roomTypes; + } + + /** + * @param string $roomTypes + */ + public function setRoomTypes($roomTypes) { + $this->roomTypes = $roomTypes; + } + + /** + * @return string + */ + public function getAdditionalInformation() { + return $this->additionalInformation; + } + + /** + * @param string $additionalInformation + */ + public function setAdditionalInformation($additionalInformation) { + $this->additionalInformation = $additionalInformation; + } + + /** + * @return float + */ + public function getLatitude() + { + if (empty($this->latitude) && $this->region !== null) { + return $this->region->getLatitude(); + } + + return $this->latitude; + } + + /** + * @param float $latitude + */ + public function setLatitude($latitude) + { + $this->latitude = $latitude; + } + + /** + * @return float + */ + public function getLongitude() + { + if (empty($this->longitude) && $this->region !== null) { + return $this->region->getLongitude(); + } + + return $this->longitude; + } + + /** + * @param float $longitude + */ + public function setLongitude($longitude) + { + $this->longitude = $longitude; + } + + /** + * @return string + */ + public function getAddress() + { + return $this->address; + } + + /** + * @param string $address + */ + public function setAddress($address) + { + $this->address = $address; + } + + /** + * @return ObjectStorage $images + */ + public function getImages() + { + return $this->images; + } + + /** + * @param ObjectStorage $images + */ + public function setImages(ObjectStorage $images) + { + $this->images = $images; + } + + /** + * @return ObjectStorage $resellerImages + */ + public function getResellerImages() + { + return $this->resellerImages; + } + + /** + * @param ObjectStorage $resellerImages + */ + public function setResellerImages($resellerImages) + { + $this->resellerImages = $resellerImages; + } + + /** + * @return ObjectStorage + */ + public function getTeaserImages() + { + return $this->teaserImages; + } + + /** + * @return null|ObjectStorage + */ + public function getTeaserImage() + { + if ($this->teaserImages->count() === 0) { + return null; + } + + $images = $this->teaserImages->toArray(); + + return reset($images); + } + + /** + * @param ObjectStorage $teaserImages + */ + public function setTeaserImages($teaserImages) + { + $this->teaserImages = $teaserImages; + } + + /** + * @return ObjectStorage + */ + public function getHeaderImages() + { + $headerImages = $this->headerImages; + + if ($headerImages->count() === 0 && null !== $this->getCity()) { + $headerImages = $this->getCity()->getHeaderImages(); + } + + return $headerImages; + } + + /** + * @return null|ObjectStorage + */ + public function getHeaderImage() + { + if ($this->headerImages->count() === 0) { + return null; + } + + $images = $this->headerImages->toArray(); + + return reset($images); + } + + /** + * @param ObjectStorage $headerImages + */ + public function setHeaderImages($headerImages) + { + $this->headerImages = $headerImages; + } + + /** + * @return string $code + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @param MatchCode $code + */ + public function addAdditionalCode(MatchCode $code) + { + $this->additionalCodes->attach($code); + } + + /** + * @param MatchCode $codeToRemove + */ + public function removeAdditionalCodes(MatchCode $codeToRemove) + { + $this->additionalCodes->detach($codeToRemove); + } + + /** + * @return ObjectStorage $codes + */ + public function getAdditionalCodes() + { + return $this->additionalCodes; + } + + /** + * @param ObjectStorage $codes + */ + public function setAdditionalCodes(ObjectStorage $codes) + { + $this->additionalCodes = $codes; + } + + /** + * @return int $type + */ + public function getType() + { + return $this->type; + } + + /** + * @param int $type + */ + public function setType($type) + { + $this->type = $type; + } + + /** + * @return int $comfort + */ + public function getCategory() + { + return $this->category; + } + + /** + * @return string + */ + public function getCategoryLabel() + { + if ($category = $this->getCategory()) { + return static::$categoryLabels[$category]; + } + + return null; + } + + /** + * @param int $category + */ + public function setCategory($category) + { + $this->category = $category; + } + + /** + * @return Country $country + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param Country $country + */ + public function setCountry(Country $country) + { + $this->country = $country; + } + + /** + * @return Region $region + */ + public function getRegion() + { + return $this->region; + } + + /** + * @param Region $region + */ + public function setRegion(Region $region) + { + $this->region = $region; + } + + /** + * @return City $city + */ + public function getCity() + { + return $this->city; + } + + /** + * @param City $city + */ + public function setCity(City $city) + { + $this->city = $city; + } + + /** + * @param Teammember $teamer + */ + public function addTeamer(Teammember $teamer) + { + $this->teamer->attach($teamer); + } + + /** + * @param Teammember $teamerToRemove The Teammember to be removed + */ + public function removeTeamer(Teammember $teamerToRemove) + { + $this->teamer->detach($teamerToRemove); + } + + /** + * @return ObjectStorage $teamer + */ + public function getTeamer() + { + return $this->teamer; + } + + /** + * @param ObjectStorage $teamer + */ + public function setTeamer(ObjectStorage $teamer) + { + $this->teamer = $teamer; + } + + /** + * @param Product $product + */ + public function addProduct(Product $product) + { + $this->products->attach($product); + } + + /** + * @param Product $product + */ + public function removeProduct(Product $product) + { + $this->products->detach($product); + } + + /** + * @return ObjectStorage + */ + public function getProducts() + { + return $this->products; + } + + /** + * @param ObjectStorage $products + */ + public function setProducts(ObjectStorage $products) + { + $this->products = $products; + } + + /** + * @param ObjectStorage $rooms + */ + public function setRooms($rooms) { + $this->rooms = $rooms; + } + + /** + * @param Fact $fact + */ + public function addFact(Fact $fact) + { + $this->facts->attach($fact); + } + + /** + * @param Fact $factToRemove + */ + public function removeFact(Fact $factToRemove) + { + $this->facts->detach($factToRemove); + } + + /** + * @return ObjectStorage + */ + public function getFacts() + { + return $this->facts; + } + + /** + * @param ObjectStorage $facts + */ + public function setFacts(ObjectStorage $facts) + { + $this->facts = $facts; + } + + /** + * @return string + */ + public function getExternalLink() + { + return $this->externalLink; + } + + /** + * @return string + */ + public function getPathSegment() + { + return $this->pathSegment; + } + + /** + * @param string $externalLink + */ + public function setExternalLink($externalLink) + { + $this->externalLink = $externalLink; + } + + /** + * @return bool + */ + public function getIsApartment() + { + return $this->getType() === self::TYPE_APPARTMENT; + } + + /** + * @return bool + */ + public function getIsHolidayFlat() + { + return $this->getType() === self::TYPE_HOLIDAY_FLAT; + } + + /** + * @return bool + */ + public function getIsOther() + { + return !$this->getIsApartment() && !$this->getIsHolidayFlat(); + } + + /** + * @return ObjectStorage + */ + public function getGroupsPriceConfigs() + { + return $this->groupsPriceConfigs; + } + + /** + * @param ObjectStorage $groupPriceConfigs + */ + public function setGroupsPriceConfigs(ObjectStorage $groupPriceConfigs) + { + $this->groupsPriceConfigs = $groupPriceConfigs; + } + + /** + * @return ObjectStorage + */ + public function getGroupsPriceOptions() + { + return $this->groupsPriceOptions; + } + + /** + * @param ObjectStorage $groupsPriceOptions + */ + public function setGroupsPriceOptions(ObjectStorage $groupsPriceOptions) + { + $this->groupsPriceOptions = $groupsPriceOptions; + } + + /** + * @return ObjectStorage + */ + public function getGroupsPriceBoards() + { + return $this->groupsPriceBoards; + } + + /** + * @param ObjectStorage $groupsPriceBoards + */ + public function setGroupsPriceBoards(ObjectStorage $groupsPriceBoards) + { + $this->groupsPriceBoards = $groupsPriceBoards; + } + + public function getGroupsPriceAdolescentsAge() + { + return $this->groupsPriceAdolescentsAge; + } + + public function setGroupsPriceAdolescentsAge($groupsPriceAdolescentsAge) + { + $this->groupsPriceAdolescentsAge = $groupsPriceAdolescentsAge; + } + + public function getGroupsPriceTaxLabel() + { + return $this->groupsPriceTaxLabel; + } + + public function setGroupsPriceTaxLabel($groupsPriceTaxLabel) + { + $this->groupsPriceTaxLabel = $groupsPriceTaxLabel; + } + + public function getMainSeasonFrom() + { + return $this->mainSeasonFrom; + } + + public function setMainSeasonFrom(\DateTime $mainSeasonFrom) + { + $this->mainSeasonFrom = $mainSeasonFrom; + } + + public function getMainSeasonTo() + { + return $this->mainSeasonTo; + } + + public function setMainSeasonTo(\DateTime $mainSeasonTo) + { + $this->mainSeasonTo = $mainSeasonTo; + } + + public function getBedsCount() + { + return $this->bedsCount; + } + + public function setBedsCount($bedsCount) + { + $this->bedsCount = $bedsCount; + } + + public function getParkingCount() + { + return $this->parkingCount; + } + + public function setParkingCount($parkingCount) + { + $this->parkingCount = $parkingCount; + } + + public function getAltitude() + { + return $this->altitude; + } + + public function setAltitude($altitude) + { + $this->altitude = $altitude; + } + + public function getShower() + { + return $this->shower; + } + + public function setShower($shower) + { + $this->shower = $shower; + } + + public function isPartyroom() + { + return $this->partyroom; + } + + public function setPartyroom($partyroom) + { + $this->partyroom = $partyroom; + } + + public function isGamesroom() + { + return $this->gamesroom; + } + + public function setGamesroom($gamesroom) + { + $this->gamesroom = $gamesroom; + } + + public function isSauna() + { + return $this->sauna; + } + + public function setSauna($sauna) + { + $this->sauna = $sauna; + } + + public function isWifi() + { + return $this->wifi; + } + + public function setWifi($wifi) + { + $this->wifi = $wifi; + } + + public function getDistanceBus() + { + return $this->distanceBus; + } + + public function setDistanceBus($distanceBus) + { + $this->distanceBus = $distanceBus; + } + + public function getDistanceLift() + { + return $this->distanceLift; + } + + public function setDistanceLift($distanceLift) + { + $this->distanceLift = $distanceLift; + } + + public function getIcons(): array + { + return HotelIconUtility::getIcons([ + 'beds_count' => $this->bedsCount, + 'parking_count' => $this->parkingCount, + 'altitude' => $this->altitude, + 'distance_bus' => $this->distanceBus, + 'distance_lift' => $this->distanceLift, + 'shower' => $this->shower, + 'partyroom' => $this->partyroom, + 'gamesroom' => $this->gamesroom, + 'sauna' => $this->sauna, + 'wifi' => $this->wifi, + ]); + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Infobox.php b/packages/ep_products/Classes/Domain/Model/Infobox.php new file mode 100644 index 0000000..d7884b5 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Infobox.php @@ -0,0 +1,73 @@ +title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + /** + * @return bool + */ + public function isDefaultText() + { + return $this->defaultText; + } + + /** + * @param bool $defaultText + */ + public function setDefaultText($defaultText) + { + $this->defaultText = $defaultText; + } +} + diff --git a/packages/ep_products/Classes/Domain/Model/Journey.php b/packages/ep_products/Classes/Domain/Model/Journey.php new file mode 100644 index 0000000..cc54f3b --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Journey.php @@ -0,0 +1,117 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Journey extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $byCar = ''; + + /** + * @var string + */ + protected $byBus; + + /** + * @var \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + protected $image; + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getByCar() + { + return $this->byCar; + } + + /** + * @param string $byCar + */ + public function setByCar($byCar) + { + $this->byCar = $byCar; + } + + /** + * @return string + */ + public function getByBus() + { + return $this->byBus; + } + + /** + * @param string $byBus + */ + public function setByBus($byBus) + { + $this->byBus = $byBus; + } + + /** + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getImage() + { + return $this->image; + } + + /** + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $image + */ + public function setImage($image) + { + $this->image = $image; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/MatchCode.php b/packages/ep_products/Classes/Domain/Model/MatchCode.php new file mode 100644 index 0000000..d0d06ea --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/MatchCode.php @@ -0,0 +1,54 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class MatchCode extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $code = ''; + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + +} \ No newline at end of file diff --git a/packages/ep_products/Classes/Domain/Model/Officetip.php b/packages/ep_products/Classes/Domain/Model/Officetip.php new file mode 100644 index 0000000..fa00363 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Officetip.php @@ -0,0 +1,96 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Officetip extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $label = ''; + + /** + * @var string + */ + protected $text = ''; + + /** + * @var \EP\EpProducts\Domain\Model\Teammember + */ + protected $author; + + /** + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * @param string $label + */ + public function setLabel($label) + { + $this->label = $label; + } + + /** + * @return string $text + */ + public function getText() + { + return $this->text; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + /** + * @return Teammember $author + */ + public function getAuthor() + { + return $this->author; + } + + /** + * @param Teammember $author + */ + public function setAuthor(Teammember $author) + { + $this->author = $author; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Product.php b/packages/ep_products/Classes/Domain/Model/Product.php new file mode 100644 index 0000000..d268f7f --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Product.php @@ -0,0 +1,1209 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements TeaserInterface +{ + + /** + * @var bool + */ + protected $searchable = true; + + /** + * @var bool + */ + protected $bookable = true; + + /** + * @var bool + */ + protected $hideBookingButton = false; + + /** + * @var bool + */ + protected $daytrip = false; + + /** + * @var bool + */ + protected $important; + + /** + * @var bool + */ + protected $skipassIncluded = true; + + /** + * @var string + */ + protected $detailPage; + + /** + * @var string + */ + protected $externalLink = ''; + + /** + * @var string + */ + protected $tourLink; + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $nameInternal = ''; + + /** + * @var string + */ + protected $keywords = ''; + + /** + * @var string + */ + protected $headline = ''; + + /** + * @var int + */ + protected $busProId = 0; + + /** + * @var string + */ + protected $code = ''; + + /** + * @var string + */ + protected $season = 'w'; + + /** + * @var string + */ + protected $feature = ''; + + /** + * @var string + */ + protected $teaser = ''; + + /** + * @var string + */ + protected $teaserLong = ''; + + /** + * @var string + */ + protected $headerTitle = ''; + + /** + * @var string + */ + protected $headerSubtitle = ''; + + /** + * @var string + */ + protected $conceptDescription = ''; + + /** + * @var string + */ + protected $boardDescription = ''; + + /** + * @var string + */ + protected $additionalServices = ''; + + /** + * @var string + */ + protected $includedServices; + + /** + * @var string + */ + protected $programmeDescription = ''; + + /** + * @var string + */ + protected $skiPassAltTitle = ''; + + /** + * @var string + */ + protected $skiPassDescription = ''; + + /** + * @var float + */ + protected $ratingAverage = 0; + + /** + * @var int + */ + protected $ratingVotesCount = 0; + + /** + * @var string + */ + protected $testimonials = ''; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $images; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $resellerImages; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $teaserImages; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $headerImages; + + /** + * @var FileReference + */ + protected $tourThumbnail; + + /** + * @var \EP\EpProducts\Domain\Model\Concept + */ + protected $concept; + + /** + * @var \EP\EpProducts\Domain\Model\Country + */ + protected $country; + + /** + * @var \EP\EpProducts\Domain\Model\Region + */ + protected $region; + + /** + * @var \EP\EpProducts\Domain\Model\City + */ + protected $city; + + /** + * @var \EP\EpProducts\Domain\Model\Journey + */ + protected $journey; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> + */ + protected $facts; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Hotel> + */ + protected $hotels; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Faq> + */ + protected $faqs; + + /** + * @var \EP\EpProducts\Domain\Model\Officetip + */ + protected $officetip; + + /** + * @var \EP\EpProducts\Domain\Model\Hotel + */ + protected $calendarHotel; + + /** + * @var \EP\EpProducts\Domain\Model\Product + */ + protected $altProduct; + + /** + * @var string + */ + protected $altLabel; + + /** + * @var string + */ + protected $video = ''; + + /** + * @var \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + protected $banner; + + /** + * @var \EP\EpProducts\Domain\Model\Badge + */ + protected $badge; + + /** + * @var string + */ + protected $nonBookableDates; + + /** + * @var int + */ + protected $minPrice; + + /** + * @var \EP\EpProducts\Domain\Model\Infobox + */ + protected $nonBookableInfo; + + public function __construct() + { + $this->initStorageObjects(); + } + + protected function initStorageObjects() + { + $this->images = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->teaserImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->headerImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->facts = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->hotels = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->faqs = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return bool + */ + public function getSearchable() + { + return $this->searchable; + } + + /** + * @param bool $searchable + */ + public function setSearchable($searchable) + { + $this->searchable = (bool) $searchable; + } + + /** + * @return boolean + */ + public function getBookable() + { + return (bool) $this->bookable; + } + + /** + * @param boolean $bookable + */ + public function setBookable($bookable) + { + $this->bookable = (bool) $bookable; + } + + /** + * @return bool + */ + public function getHideBookingButton() + { + return $this->hideBookingButton; + } + + /** + * @param bool $hideBookingButton + */ + public function setHideBookingButton($hideBookingButton) + { + $this->hideBookingButton = (bool) $hideBookingButton; + } + + /** + * @return bool + */ + public function isDaytrip() + { + return (bool) $this->daytrip; + } + + /** + * @param bool $daytrip + */ + public function setDaytrip($daytrip) + { + $this->daytrip = (bool) $daytrip; + } + + /** + * @return bool + */ + public function isImportant() + { + return $this->important; + } + + /** + * @param bool $important + */ + public function setImportant($important) + { + $this->important = $important; + } + + /** + * @return bool + */ + public function isSkipassIncluded() + { + return $this->skipassIncluded; + } + + /** + * @param bool $skipassIncluded + */ + public function setSkipassIncluded($skipassIncluded) + { + $this->skipassIncluded = $skipassIncluded; + } + + /** + * @return string + */ + public function getDetailPage() { + return $this->detailPage; + } + + /** + * @param string $detailPage + */ + public function setDetailPage($detailPage) { + $this->detailPage = $detailPage; + } + + /** + * @return string + */ + public function getExternalLink() + { + return $this->externalLink; + } + + /** + * @param string $externalLink + */ + public function setExternalLink($externalLink) + { + $this->externalLink = $externalLink; + } + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getNameInternal() + { + return $this->nameInternal; + } + + /** + * @param string $nameInternal + */ + public function setNameInternal($nameInternal) + { + $this->nameInternal = $nameInternal; + } + + /** + * @return string + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * @param string $keywords + */ + public function setKeywords($keywords) + { + $this->keywords = $keywords; + } + + /** + * @return string + */ + public function getHeadline() { + return $this->headline; + } + + /** + * @param string $headline + */ + public function setHeadline($headline) { + $this->headline = $headline; + } + + /** + * @return int + */ + public function getBusProId() + { + return $this->busProId; + } + + /** + * @param int $busProId + */ + public function setBusProId($busProId) + { + $this->busProId = $busProId; + } + + /** + * @return string $code + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @return string + */ + public function getSeason() + { + return $this->season; + } + + /** + * @param string $season + */ + public function setSeason($season) + { + $this->season = $season; + } + + /** + * @return string + */ + public function getFeature() + { + return $this->feature; + } + + /** + * @param string $feature + */ + public function setFeature($feature) + { + $this->feature = $feature; + } + + /** + * @return string + */ + public function getTeaser() { + return $this->teaser; + } + + /** + * @param string $teaser + */ + public function setTeaser($teaser) { + $this->teaser = $teaser; + } + + /** + * @return string + */ + public function getTeaserLong() + { + return $this->teaserLong; + } + + /** + * @param string $teaserLong + */ + public function setTeaserLong($teaserLong) + { + $this->teaserLong = $teaserLong; + } + + /** + * @return string + */ + public function getHeaderTitle() + { + return $this->headerTitle; + } + + /** + * @param string $headerTitle + */ + public function setHeaderTitle($headerTitle) + { + $this->headerTitle = $headerTitle; + } + + /** + * @return string + */ + public function getHeaderSubtitle() + { + return $this->headerSubtitle; + } + + /** + * @param string $headerSubtitle + */ + public function setHeaderSubtitle($headerSubtitle) + { + $this->headerSubtitle = $headerSubtitle; + } + + /** + * @return string + */ + public function getConceptDescription() { + return $this->conceptDescription; + } + + /** + * @param string $conceptDescription + */ + public function setConceptDescription($conceptDescription) { + $this->conceptDescription = $conceptDescription; + } + + /** + * @return string + */ + public function getBoardDescription() { + return $this->boardDescription; + } + + /** + * @param string $boardDescription + */ + public function setBoardDescription($boardDescription) { + $this->boardDescription = $boardDescription; + } + + /** + * @return string + */ + public function getAdditionalServices() { + return $this->additionalServices; + } + + /** + * @param string $additionalServices + */ + public function setAdditionalServices($additionalServices) { + $this->additionalServices = $additionalServices; + } + + /** + * @return string + */ + public function getIncludedServices() + { + return $this->includedServices; + } + + /** + * @param string $includedServices + */ + public function setIncludedServices($includedServices) + { + $this->includedServices = $includedServices; + } + + /** + * @return string + */ + public function getProgrammeDescription() { + return $this->programmeDescription; + } + + /** + * @param string $programmeDescription + */ + public function setProgrammeDescription($programmeDescription) { + $this->programmeDescription = $programmeDescription; + } + + /** + * @return string + */ + public function getSkiPassAltTitle() + { + return $this->skiPassAltTitle; + } + + /** + * @param string $skiPassAltTitle + */ + public function setSkiPassAltTitle($skiPassAltTitle) + { + $this->skiPassAltTitle = $skiPassAltTitle; + } + + /** + * @return string + */ + public function getSkiPassDescription() { + return $this->skiPassDescription; + } + + /** + * @param string $skiPassDescription + */ + public function setSkiPassDescription($skiPassDescription) { + $this->skiPassDescription = $skiPassDescription; + } + + /** + * @return float + */ + public function getRatingAverage() + { + return $this->ratingAverage; + } + + /** + * @param float $ratingAverage + */ + public function setRatingAverage($ratingAverage) + { + $this->ratingAverage = $ratingAverage; + } + + /** + * @return int + */ + public function getRatingVotesCount() + { + return $this->ratingVotesCount; + } + + /** + * @param int $ratingVotesCount + */ + public function setRatingVotesCount($ratingVotesCount) + { + $this->ratingVotesCount = $ratingVotesCount; + } + + /** + * @return string + */ + public function getTestimonials() + { + return $this->testimonials; + } + + /** + * @param string $testimonials + */ + public function setTestimonials($testimonials) + { + $this->testimonials = $testimonials; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference $images> + */ + public function getImages() + { + return $this->images; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $images + */ + public function setImages(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $images) + { + $this->images = $images; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference $resellerImages> + */ + public function getResellerImages() + { + if (!$this->resellerImages) { + return $this->images; + } + + return $this->resellerImages; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $images + */ + public function setResellerImages(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $images) + { + $this->resellerImages = $images; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImages() + { + return $this->teaserImages; + } + + /** + * @return null|\TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImage() + { + $teaserImages = $this->teaserImages; + $teaserImages->rewind(); + + if ($teaserImages->count() === 0) { + return null; + } + + return $teaserImages->current(); + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $teaserImages + */ + public function setTeaserImages($teaserImages) + { + $this->teaserImages = $teaserImages; + } + + /** + * Returns all header images with fallback to parent + * region in case none are assigned. + * + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImages() + { + $headerImages = $this->headerImages; + + if ($headerImages->count() === 0 && null !== $this->getRegion()) { + $headerImages = $this->getRegion()->getHeaderImages(); + } + + return $headerImages; + } + + /** + * Returns a single header image with fallback to parent + * region in case none is assigned. + * + * @return null|\TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getHeaderImage() + { + $headerImages = $this->getHeaderImages(); + $headerImages->rewind(); + + if ($headerImages->count() === 0) { + return null; + } + + return $headerImages->current(); + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $headerImages + */ + public function setHeaderImages($headerImages) + { + $this->headerImages = $headerImages; + } + + /** + * @return \EP\EpProducts\Domain\Model\Concept $concept + */ + public function getConcept() + { + return $this->concept; + } + + /** + * @param \EP\EpProducts\Domain\Model\Concept $concept + */ + public function setConcepts(\EP\EpProducts\Domain\Model\Concept $concept) + { + $this->concept = $concept; + } + + /** + * @return \EP\EpProducts\Domain\Model\Country $country + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param \EP\EpProducts\Domain\Model\Country $country + */ + public function setCountry(\EP\EpProducts\Domain\Model\Country $country) + { + $this->country = $country; + } + + /** + * @return \EP\EpProducts\Domain\Model\Region + */ + public function getRegion() + { + return $this->region; + } + + /** + * @param \EP\EpProducts\Domain\Model\Region $region + */ + public function setRegion(\EP\EpProducts\Domain\Model\Region $region) + { + $this->region = $region; + } + + /** + * @return \EP\EpProducts\Domain\Model\City + */ + public function getCity() + { + return $this->city; + } + + /** + * @param \EP\EpProducts\Domain\Model\City $city + */ + public function setCity(\EP\EpProducts\Domain\Model\City $city) + { + $this->city = $city; + } + + /** + * @return \EP\EpProducts\Domain\Model\Journey + */ + public function getJourney() + { + return $this->journey; + } + + /** + * @param \EP\EpProducts\Domain\Model\Journey $journey + */ + public function setJourney($journey) + { + $this->journey = $journey; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function getFacts() + { + return $this->facts; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function setFacts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $facts) + { + $this->facts = $facts; + } + + /** + * @param \EP\EpProducts\Domain\Model\Hotel $hotel + */ + public function addHotel(\EP\EpProducts\Domain\Model\Hotel $hotel) + { + $this->hotels->attach($hotel); + } + + /** + * @param \EP\EpProducts\Domain\Model\Hotel $hotel + */ + public function removeHotel(\EP\EpProducts\Domain\Model\Hotel $hotel) + { + $this->hotels->detach($hotel); + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Hotel> $hotels + */ + public function getHotels() + { + return $this->hotels; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Hotel> $hotels + */ + public function setHotels(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $hotels) + { + $this->hotels = $hotels; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Faq> + */ + public function getFaqs() { + return $this->faqs; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Faq> $faq + */ + public function setFaqs(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $faqs) + { + $this->faqs = $faqs; + } + + /** + * @return Officetip + */ + public function getOfficetip() + { + return $this->officetip; + } + + /** + * @param Officetip $officetip + */ + public function setOfficetip($officetip) + { + $this->officetip = $officetip; + } + + /** + * @return Hotel + */ + public function getCalendarHotel() + { + return $this->calendarHotel; + } + + /** + * @param Hotel $calendarHotel + */ + public function setCalendarHotel($calendarHotel) + { + $this->calendarHotel = $calendarHotel; + } + + /** + * @return Product + */ + public function getAltProduct() + { + return $this->altProduct; + } + + /** + * @param Product $altProduct + */ + public function setAltProduct($altProduct) + { + $this->altProduct = $altProduct; + } + + /** + * @return string + */ + public function getAltLabel() + { + return $this->altLabel; + } + + /** + * @param string $altLabel + */ + public function setAltLabel($altLabel) + { + $this->altLabel = $altLabel; + } + + /** + * @return string + */ + public function getVideo() + { + return $this->video; + } + + /** + * @param string $video + */ + public function setVideo($video) + { + $this->video = $video; + } + + /** + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getBanner() + { + return $this->banner; + } + + /** + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $banner + */ + public function setBanner($banner) + { + $this->banner = $banner; + } + + /** + * @return Badge + */ + public function getBadge() + { + return $this->badge; + } + + /** + * @param Badge $badge + */ + public function setBadge($badge) + { + $this->badge = $badge; + } + + /** + * @return string + */ + public function getNonBookableDates() + { + return $this->nonBookableDates; + } + + /** + * @return int + */ + public function getMinPrice(): int + { + return $this->minPrice; + } + + /** + * @param int $minPrice + */ + public function setMinPrice($minPrice) + { + $this->minPrice = $minPrice; + } + + /** + * @return Infobox + */ + public function getNonBookableInfo() + { + return $this->nonBookableInfo; + } + + /** + * @param Infobox $nonBookableInfo + */ + public function setNonBookableInfo($nonBookableInfo) + { + $this->nonBookableInfo = $nonBookableInfo; + } + + public function getTourThumbnail() + { + return $this->tourThumbnail; + } + + public function setTourThumbnail(FileReference $tourThumbnail) + { + $this->tourThumbnail = $tourThumbnail; + } + + public function getTourLink() + { + return $this->tourLink; + } + + public function setTourLink(string $tourLink) + { + $this->tourLink = $tourLink; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Region.php b/packages/ep_products/Classes/Domain/Model/Region.php new file mode 100644 index 0000000..4f50d29 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Region.php @@ -0,0 +1,476 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Region extends AbstractDestination implements TeaserInterface +{ + + /** + * @var bool + */ + protected $excludeFromTeasergroups = false; + + /** + * @var string + */ + protected $skiArea = ''; + + /** + * @var string + */ + protected $skiAreaExtended = ''; + + /** + * @var string + */ + protected $skiPass = ''; + + /** + * @var string + */ + protected $news = ''; + + /** + * @var string + */ + protected $length = ''; + + /** + * @var string + */ + protected $height = ''; + + /** + * @var string + */ + protected $lifts = ''; + + /** + * @var string + */ + protected $feature = ''; + + /** + * @var \EP\EpProducts\Domain\Model\Country + */ + protected $country; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\City> + * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") + */ + protected $cities; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Product> + * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") + */ + protected $products; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> + */ + protected $facts; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Hotel> + * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") + */ + protected $hotels; + + /** + * @var \EP\EpProducts\Domain\Model\Snowreport + */ + protected $snowreport; + + /** + * @var \EP\EpProducts\Domain\Model\Webcam + */ + protected $webcam; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $regionMaps; + + /** + * @var \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + protected $awardImage; + + /** + * @var string + */ + protected $awardLink; + + /** + * @var string + */ + protected $awardLabel; + + protected function initStorageObjects() + { + parent::initStorageObjects(); + $this->cities = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->products = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->facts = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->hotels = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->regionMaps = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return bool + */ + public function isExcludeFromTeasergroups() + { + return $this->excludeFromTeasergroups; + } + + /** + * @param bool $excludeFromTeasergroups + */ + public function setExcludeFromTeasergroups($excludeFromTeasergroups) + { + $this->excludeFromTeasergroups = (bool) $excludeFromTeasergroups; + } + + /** + * @return string + */ + public function getSkiArea() { + return $this->skiArea; + } + + /** + * @param string $skiArea + */ + public function setSkiArea($skiArea) { + $this->skiArea = $skiArea; + } + + /** + * @return string + */ + public function getSkiAreaExtended() + { + return $this->skiAreaExtended; + } + + /** + * @param string $skiAreaExtended + */ + public function setSkiAreaExtended($skiAreaExtended) + { + $this->skiAreaExtended = $skiAreaExtended; + } + + /** + * @return string + */ + public function getSkiPass() { + return $this->skiPass; + } + + /** + * @param string $skiPass + */ + public function setSkiPass($skiPass) { + $this->skiPass = $skiPass; + } + + /** + * @return string + */ + public function getNews() { + return $this->news; + } + + /** + * @param string $news + */ + public function setNews($news) { + $this->news = $news; + } + + /** + * @return string + */ + public function getLength() { + return $this->length; + } + + /** + * @param string $length + */ + public function setLength($length) { + $this->length = $length; + } + + /** + * @return string + */ + public function getHeight() { + return $this->height; + } + + /** + * @param string $height + */ + public function setHeight($height) { + $this->height = $height; + } + + /** + * @return string + */ + public function getLifts() { + return $this->lifts; + } + + /** + * @param string $lifts + */ + public function setLifts($lifts) { + $this->lifts = $lifts; + } + + /** + * @return string + */ + public function getFeature() + { + return $this->feature; + } + + /** + * @param string $feature + */ + public function setFeature($feature) + { + $this->feature = $feature; + } + + /** + * Returns all header images with fallback to parent + * country in case none are assigned. + * + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImages() + { + $headerImages = $this->headerImages; + + if ($headerImages->count() === 0 && null !== $this->getCountry()) { + $headerImages = $this->getCountry()->getHeaderImages(); + } + + return $headerImages; + } + + /** + * @return \EP\EpProducts\Domain\Model\Country $country + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param \EP\EpProducts\Domain\Model\Country $country + */ + public function setCountry(\EP\EpProducts\Domain\Model\Country $country) + { + $this->country = $country; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getCities() { + return $this->cities; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $cities + */ + public function setCities($cities) { + $this->cities = $cities; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getProducts() { + return $this->products; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $products + */ + public function setProducts($products) { + $this->products = $products; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function getFacts() + { + return $this->facts; + } + + /** + * Sets the facts + * + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function setFacts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $facts) + { + $this->facts = $facts; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHotels() { + return $this->hotels; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $hotels + */ + public function setHotels($hotels) { + $this->hotels = $hotels; + } + + /** + * @return Snowreport + */ + public function getSnowreport() + { + return $this->snowreport; + } + + /** + * @param Snowreport $snowreport + */ + public function setSnowreport($snowreport) + { + $this->snowreport = $snowreport; + } + + /** + * @return Webcam + */ + public function getWebcam() + { + return $this->webcam; + } + + /** + * @return bool + */ + public function getWebcamAvailable() + { + $webcam = $this->getWebcam(); + return $webcam !== null && $webcam->getAvailable(); + } + + /** + * @param Webcam $webcam + */ + public function setWebcam($webcam) + { + $this->webcam = $webcam; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + public function getRegionMaps() + { + return $this->regionMaps; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + public function setRegionMaps($regionMaps) + { + $this->regionMaps = $regionMaps; + } + + /** + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getAwardImage() + { + return $this->awardImage; + } + + /** + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $awardImage + */ + public function setAwardImage($awardImage) + { + $this->awardImage = $awardImage; + } + + /** + * @return string + */ + public function getAwardLink() + { + return $this->awardLink; + } + + /** + * @param string $awardLink + */ + public function setAwardLink($awardLink) + { + $this->awardLink = $awardLink; + } + + /** + * @return string + */ + public function getAwardLabel() + { + return $this->awardLabel; + } + + /** + * @param string $awardLabel + */ + public function setAwardLabel($awardLabel) + { + $this->awardLabel = $awardLabel; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Reseller.php b/packages/ep_products/Classes/Domain/Model/Reseller.php new file mode 100644 index 0000000..7fbc41d --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Reseller.php @@ -0,0 +1,127 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Reseller extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $name; + + /** + * @var string + */ + protected $code; + + /** + * @var string + */ + protected $paCode; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Product> + * @Lazy() + */ + protected $products; + + public function __construct() + { + $this->products = new ObjectStorage(); + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @return string + */ + public function getPaCode() + { + return $this->paCode; + } + + /** + * @param string $paCode + */ + public function setPaCode($paCode) + { + $this->paCode = $paCode; + } + + /** + * @return ObjectStorage + */ + public function getProducts() + { + return $this->products; + } + + /** + * @param ObjectStorage $products + */ + public function setProducts($products) + { + $this->products = $products; + } + +} + diff --git a/packages/ep_products/Classes/Domain/Model/Room.php b/packages/ep_products/Classes/Domain/Model/Room.php new file mode 100644 index 0000000..16c5f41 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Room.php @@ -0,0 +1,48 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Room +{ + + const ROOM_TYPE_UNCLASSIFIED = 0; + const ROOM_TYPE_FOR_ONE = 1; + const ROOM_TYPE_FOR_TWO = 2; + const ROOM_TYPE_FOR_THREE = 3; + const ROOM_TYPE_FOR_FOUR = 4; + const ROOM_TYPE_FOR_FIVE = 5; + const ROOM_TYPE_FOR_SIX = 6; + const ROOM_TYPE_FOR_SEVEN = 7; + const ROOM_TYPE_FOR_EIGHT = 8; + const ROOM_TYPE_FOR_NINE_PLUS = 9; + const ROOM_TYPE_WITH_OTHERS = 10; + + const ROOM_STATUS_AVAILABLE = 1; + const ROOM_STATUS_ON_REQUEST = 2; + const ROOM_STATUS_FULLY_BOOKED = 3; +} diff --git a/packages/ep_products/Classes/Domain/Model/SearchResult.php b/packages/ep_products/Classes/Domain/Model/SearchResult.php new file mode 100644 index 0000000..e4b8395 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/SearchResult.php @@ -0,0 +1,85 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class SearchResult +{ + + /** + * @var int + */ + protected $total = 0; + + /** + * @var array + */ + protected $data = []; + + /** + * @param array $data + * @param int $total + */ + public function __construct(array $data = [], $total = 0) + { + $this->data = $data; + $this->total = $total; + } + + /** + * @return int + */ + public function getTotal() + { + return $this->total; + } + + /** + * @param int $total + */ + public function setTotal($total) + { + $this->total = $total; + } + + /** + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * @param array $data + */ + public function setData($data) + { + $this->data = $data; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Snowreport.php b/packages/ep_products/Classes/Domain/Model/Snowreport.php new file mode 100644 index 0000000..7497f42 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Snowreport.php @@ -0,0 +1,392 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Snowreport extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var int + */ + protected $vendorUid; + + /** + * @var string + */ + protected $regionName = ''; + + /** + * @var bool + */ + protected $glacier = false; + + /** + * @var int + */ + protected $heightMax = 0; + + /** + * @var int + */ + protected $heightMin = 0; + + /** + * @var bool + */ + protected $opened = false; + + /** + * @var int + */ + protected $snowLevelMountain = 0; + + /** + * @var int + */ + protected $snowLevelValley = 0; + + /** + * @var string + */ + protected $snowQuality = ''; + + /** + * @var \DateTime + */ + protected $lastSnowfall; + + /** + * @var int + */ + protected $liftsTotal = 0; + + /** + * @var int + */ + protected $liftsOpened = 0; + + /** + * @var \DateTime + */ + protected $lastUpdate; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Webcam> + */ + protected $webcams; + + /** + * @var \DateTime + */ + protected $seasonStart; + + /** + * @var \DateTime + */ + protected $seasonEnd; + + public function __construct() + { + $this->webcams = new ObjectStorage(); + } + + /** + * @return int + */ + public function getVendorUid() + { + return $this->vendorUid; + } + + /** + * @param int $vendorUid + */ + public function setVendorUid($vendorUid) + { + $this->vendorUid = $vendorUid; + } + + /** + * @return string + */ + public function getRegionName() + { + return $this->regionName; + } + + /** + * @param string $regionName + */ + public function setRegionName($regionName) + { + $this->regionName = $regionName; + } + + /** + * @return bool + */ + public function getGlacier() + { + return $this->glacier; + } + + /** + * @param bool $glacier + */ + public function setGlacier($glacier) + { + $this->glacier = $glacier; + } + + /** + * @return int + */ + public function getHeightMax() + { + return $this->heightMax; + } + + /** + * @param int $heightMax + */ + public function setHeightMax($heightMax) + { + $this->heightMax = $heightMax; + } + + /** + * @return int + */ + public function getHeightMin() + { + return $this->heightMin; + } + + /** + * @param int $heightMin + */ + public function setHeightMin($heightMin) + { + $this->heightMin = $heightMin; + } + + /** + * @return bool + */ + public function getOpened() + { + return $this->opened; + } + + /** + * @param bool $opened + */ + public function setOpened($opened) + { + $this->opened = $opened; + } + + /** + * @return int + */ + public function getSnowLevelMountain() + { + return $this->snowLevelMountain; + } + + /** + * @param int $snowLevelMountain + */ + public function setSnowLevelMountain($snowLevelMountain) + { + $this->snowLevelMountain = $snowLevelMountain; + } + + /** + * @return int + */ + public function getSnowLevelValley() + { + return $this->snowLevelValley; + } + + /** + * @param int $snowLevelValley + */ + public function setSnowLevelValley($snowLevelValley) + { + $this->snowLevelValley = $snowLevelValley; + } + + /** + * @return string + */ + public function getSnowQuality() + { + return $this->snowQuality; + } + + /** + * @param string $snowQuality + */ + public function setSnowQuality($snowQuality) + { + $this->snowQuality = $snowQuality; + } + + /** + * @return \DateTime + */ + public function getLastSnowfall() + { + return $this->lastSnowfall; + } + + /** + * @param \DateTime $lastSnowfall + */ + public function setLastSnowfall($lastSnowfall) + { + $this->lastSnowfall = $lastSnowfall; + } + + /** + * @return int + */ + public function getLiftsTotal() + { + return (int) $this->liftsTotal; + } + + /** + * @param int $liftsTotal + */ + public function setLiftsTotal($liftsTotal) + { + $this->liftsTotal = $liftsTotal; + } + + /** + * @return int + */ + public function getLiftsOpened() + { + return (int) $this->liftsOpened; + } + + /** + * @param int $liftsOpened + */ + public function setLiftsOpened($liftsOpened) + { + $this->liftsOpened = $liftsOpened; + } + + /** + * @return \DateTime + */ + public function getLastUpdate() + { + return $this->lastUpdate; + } + + /** + * @param \DateTime $lastUpdate + */ + public function setLastUpdate($lastUpdate) + { + $this->lastUpdate = $lastUpdate; + } + + /** + * @param \EP\EpProducts\Domain\Model\Webcam $webcam + */ + public function addWebcam(\EP\EpProducts\Domain\Model\Webcam $webcam) + { + $this->webcams->attach($webcam); + } + + /** + * @param \EP\EpProducts\Domain\Model\Webcam $webcam + */ + public function removeWebcam(\EP\EpProducts\Domain\Model\Webcam $webcam) + { + $this->webcams->detach($webcam); + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Webcam> + */ + public function getWebcams() + { + return $this->webcams; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Webcam> $webcams + */ + public function setWebcams(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $webcams) + { + $this->webcams = $webcams; + } + + /** + * @return \DateTime + */ + public function getSeasonStart() + { + return $this->seasonStart; + } + + /** + * @param \DateTime $seasonStart + */ + public function setSeasonStart($seasonStart) + { + $this->seasonStart = $seasonStart; + } + + /** + * @return \DateTime + */ + public function getSeasonEnd() + { + return $this->seasonEnd; + } + + /** + * @param \DateTime $seasonEnd + */ + public function setSeasonEnd($seasonEnd) + { + $this->seasonEnd = $seasonEnd; + } +} + diff --git a/packages/ep_products/Classes/Domain/Model/Teammember.php b/packages/ep_products/Classes/Domain/Model/Teammember.php new file mode 100644 index 0000000..d7d1fff --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Teammember.php @@ -0,0 +1,187 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Teammember extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $email = ''; + + /** + * @var string + */ + protected $phone = ''; + + /** + * @var string + */ + protected $tasks = ''; + + /** + * @var \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + protected $image; + + /** + * @var string + */ + protected $description = ''; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\Category> + */ + protected $categories; + + public function __construct() + { + $this->categories = new ObjectStorage(); + } + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string $email + */ + public function getEmail() + { + return $this->email; + } + + /** + * @param string $email + */ + public function setEmail($email) + { + $this->email = $email; + } + + /** + * @return string + */ + public function getPhone() + { + return $this->phone; + } + + /** + * @param string $phone + */ + public function setPhone($phone) + { + $this->phone = $phone; + } + + /** + * @return string + */ + public function getTasks() + { + return $this->tasks; + } + + /** + * @param string $tasks + */ + public function setTasks($tasks) + { + $this->tasks = $tasks; + } + + /** + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getImage() + { + return $this->image; + } + + /** + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $image + */ + public function setImage($image) + { + $this->image = $image; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\Category> + */ + public function getCategories() + { + return $this->categories; + } + + /** + * @param ObjectStorage $categories + */ + public function setCategories($categories) + { + $this->categories = $categories; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/TeaserInterface.php b/packages/ep_products/Classes/Domain/Model/TeaserInterface.php new file mode 100644 index 0000000..33ad37a --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/TeaserInterface.php @@ -0,0 +1,50 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +interface TeaserInterface +{ + + /** + * @return string + */ + public function getTeaser(); + + /** + * @return ObjectStorage + */ + public function getTeaserImages(); + + /** + * @return null|\TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImage(); + +} diff --git a/packages/ep_products/Classes/Domain/Model/Travelinfo.php b/packages/ep_products/Classes/Domain/Model/Travelinfo.php new file mode 100644 index 0000000..ad55bf2 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Travelinfo.php @@ -0,0 +1,329 @@ + + */ + protected $teasers; + + /** + * @var ObjectStorage + */ + protected $links; + + /** + * @var string + */ + protected $travelCode; + + /** + * @var Product + */ + protected $product; + + /** + * @var bool + */ + protected $hideAddress = false; + + public function __construct() + { + $this->initStorageObjects(); + } + + protected function initStorageObjects() + { + $this->teasers = new ObjectStorage(); + $this->links = new ObjectStorage(); + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + public function getHeaderImage() + { + return $this->headerImage; + } + + public function setHeaderImage($headerImage) + { + $this->headerImage = $headerImage; + + return $this; + } + + public function getFooterImage() + { + return $this->footerImage; + } + + public function setFooterImage($footerImage) + { + $this->footerImage = $footerImage; + + return $this; + } + + public function getSubline() + { + return $this->subline; + } + + public function setSubline($subline) + { + $this->subline = $subline; + + return $this; + } + + public function getHeadline() + { + return $this->headline; + } + + public function setHeadline($headline) + { + $this->headline = $headline; + + return $this; + } + + public function getIntroText() + { + return $this->introText; + } + + public function setIntroText($introText) + { + $this->introText = $introText; + + return $this; + } + + public function getBusInfoText() + { + return $this->busInfoText; + } + + public function setBusInfoText($busInfoText) + { + $this->busInfoText = $busInfoText; + + return $this; + } + + public function getDepartureText() + { + return $this->departureText; + } + + public function setDepartureText($departureText) + { + $this->departureText = $departureText; + + return $this; + } + + public function getFooterText() + { + return $this->footerText; + } + + public function setFooterText($footerText) + { + $this->footerText = $footerText; + + return $this; + } + + public function getAdditionalInfo() + { + return $this->additionalInfo; + } + + public function setAdditionalInfo($additionalInfo) + { + $this->additionalInfo = $additionalInfo; + + return $this; + } + + public function getImportantPhoneNumbers() + { + return $this->importantPhoneNumbers; + } + + public function setImportantPhoneNumbers($importantPhoneNumbers) + { + $this->importantPhoneNumbers = $importantPhoneNumbers; + + return $this; + } + + public function getImportantLinks() + { + return $this->importantLinks; + } + + public function setImportantLinks($importantLinks) + { + $this->importantLinks = $importantLinks; + + return $this; + } + + public function getSelfArrangedText() + { + return $this->selfArrangedText; + } + + public function setSelfArrangedText($selfArrangedText) + { + $this->selfArrangedText = $selfArrangedText; + + return $this; + } + + public function getTeasers() + { + return $this->teasers; + } + + public function setTeasers($teasers) + { + $this->teasers = $teasers; + } + + public function getLinks() + { + return $this->links; + } + + public function setLinks($links) + { + $this->links = $links; + + return $this; + } + + public function getTravelCode() + { + return $this->travelCode; + } + + public function setTravelCode($travelCode) + { + $this->travelCode = $travelCode; + + return $this; + } + + public function getProduct() + { + return $this->product; + } + + public function setProduct($product) + { + $this->product = $product; + return $this; + } + + public function isHideAddress() + { + return $this->hideAddress; + } + + public function setHideAddress($hideAddress) + { + $this->hideAddress = $hideAddress; + return $this; + } +} + diff --git a/packages/ep_products/Classes/Domain/Model/TravelinfoLink.php b/packages/ep_products/Classes/Domain/Model/TravelinfoLink.php new file mode 100644 index 0000000..b85bdcf --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/TravelinfoLink.php @@ -0,0 +1,61 @@ +title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + public function getLink() + { + return $this->link; + } + + public function setLink($link) + { + $this->link = $link; + } + + public function getIcon() + { + return $this->icon; + } + + public function setIcon($icon) + { + $this->icon = $icon; + } +} + diff --git a/packages/ep_products/Classes/Domain/Model/TravelinfoTeaser.php b/packages/ep_products/Classes/Domain/Model/TravelinfoTeaser.php new file mode 100644 index 0000000..ae15345 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/TravelinfoTeaser.php @@ -0,0 +1,76 @@ +title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + public function getBodytext() + { + return $this->bodytext; + } + + public function setBodytext($bodytext) + { + $this->bodytext = $bodytext; + } + + public function getCategory() + { + return $this->category; + } + + public function setCategory($category) + { + $this->category = $category; + } + + public function getImage() + { + return $this->image; + } + + public function setImage($image) + { + $this->image = $image; + } +} + diff --git a/packages/ep_products/Classes/Domain/Model/Webcam.php b/packages/ep_products/Classes/Domain/Model/Webcam.php new file mode 100644 index 0000000..52f873a --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Webcam.php @@ -0,0 +1,138 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Webcam extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var int + */ + protected $vendorUid; + + /** + * @var \EP\EpProducts\Domain\Model\Snowreport + */ + protected $snowreport; + + /** + * @var string + */ + protected $name; + + /** + * @var string + */ + protected $url; + + /** + * @var bool + */ + protected $available = true; + + /** + * @return Snowreport + */ + public function getSnowreport() + { + return $this->snowreport; + } + + /** + * @param Snowreport $snowreport + */ + public function setSnowreport($snowreport) + { + $this->snowreport = $snowreport; + } + + /** + * @return int + */ + public function getVendorUid() + { + return $this->vendorUid; + } + + /** + * @param int $vendorUid + */ + public function setVendorUid($vendorUid) + { + $this->vendorUid = $vendorUid; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string $url + */ + public function setUrl($url) + { + $this->url = $url; + } + + /** + * @return boolean + */ + public function getAvailable() + { + return $this->available; + } + + /** + * @param boolean $available + */ + public function setAvailable($available) + { + $this->available = $available; + } +} + diff --git a/packages/ep_products/Classes/Domain/Repository/AbstractRepository.php b/packages/ep_products/Classes/Domain/Repository/AbstractRepository.php new file mode 100644 index 0000000..ae9db36 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/AbstractRepository.php @@ -0,0 +1,230 @@ +objectManager->get(Typo3QuerySettings::class); + $querySettings->setRespectStoragePage(false); + $this->setDefaultQuerySettings($querySettings); + } + + /** + * @param array $uids + * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + public function getListByUids(array $uids = []) + { + $query = $this->createQuery(); + $query->matching( + $query->in('uid', $uids) + ); + return $query->execute(); + } + + protected function addFiltersettingsConditions + ( + QueryBuilder $qb, + array $filterSettings, + bool $constraintDates = true + ): void { + if ($constraintDates) { + $this->addDateFilterConditions($qb, $filterSettings); + } + $this->addPriceRangeFilterConditions($qb, $filterSettings); + $this->addCountryRegionAndCityFilterConditions($qb, $filterSettings); + $this->addConceptFilterConditions($qb, $filterSettings); + $this->addPaxFilterConditions($qb, $filterSettings); + $this->addDurationFilterConditions($qb, $filterSettings); + $this->addHotelTypesFilterConditions($qb, $filterSettings); + $this->addBoardTypesFilterConditions($qb, $filterSettings); + $this->addRoomTypesFilterConditions($qb, $filterSettings); + $this->addTransportationFilterConditions($qb, $filterSettings); + + // Product + if (!empty($filterSettings['productUid'])) { + $qb->andWhere($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter((int)$filterSettings['productUid'], Connection::PARAM_INT) + )); + } + + // Hotel + if (!empty($filterSettings['hotelUid'])) { + $qb->andWhere($qb->expr()->eq( + 'date.hotel', + $qb->createNamedParameter((int)$filterSettings['hotelUid'], Connection::PARAM_INT) + )); + } + } + + protected function addDateFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Date Start + if (!empty($filterSettings['dateFrom'])) { + $qb->andWhere($qb->expr()->gte( + 'date.date_start', + $qb->createNamedParameter($filterSettings['dateFrom']) + )); + } + + // Date End + if (!empty($filterSettings['dateTo'])) { + $qb->andWhere($qb->expr()->lte( + 'date.date_end', + $qb->createNamedParameter($filterSettings['dateTo']) + )); + } + } + + protected function addHotelFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Hotel + if ($filterSettings['hotelUid'] > 0) { + $qb->andWhere($qb->expr()->eq( + 'date.hotel', + $qb->createNamedParameter((int)$filterSettings['hotelUid'], Connection::PARAM_INT) + )); + } + + // Hotel types + if (isset($filterSettings['hotelTypes']) && is_array($filterSettings['hotelTypes']) && count($filterSettings['hotelTypes']) > 0) { + $qb->andWhere($qb->expr()->in( + 'date.hotel_type', + $qb->createNamedParameter($filterSettings['hotelTypes'], Connection::PARAM_INT_ARRAY) + )); + } + } + + protected function addCountryRegionAndCityFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + foreach (['city', 'region', 'country'] as $column) { + $key = $column . 'Uids'; + if (isset($filterSettings[$key]) && is_array($filterSettings[$key]) && count($filterSettings[$key]) > 0) { + $qb->andWhere($qb->expr()->in( + 'date.' . $column, + $qb->createNamedParameter($filterSettings[$key], Connection::PARAM_INT_ARRAY) + )); + break; + } + } + } + + protected function addConceptFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + if (isset($filterSettings['conceptUids']) && is_array($filterSettings['conceptUids']) && count($filterSettings['conceptUids']) > 0) { + $qb->andWhere($qb->expr()->in( + 'date.concept', + $qb->createNamedParameter($filterSettings['conceptUids'], Connection::PARAM_INT_ARRAY) + )); + } + } + + protected function addPaxFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Pax + if ($filterSettings['pax'] > 0) { + $qb->andWhere($qb->expr()->lte( + 'date.min_pax', + $qb->createNamedParameter($filterSettings['pax'], Connection::PARAM_INT) + )); + } + } + + protected function addPriceRangeFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Price ranges + if (isset($filterSettings['priceRanges']) && is_array($filterSettings['priceRanges']) && count($filterSettings['priceRanges']) > 0) { + $orX = $qb->expr()->orX(); + foreach ($filterSettings['priceRanges'] as $priceRange) { + [ $priceMin, $priceMax ] = FilterOptions::$priceRangeOptions[$priceRange]; + $orX->add($qb->expr()->andX( + $qb->expr()->gte( + 'date.min_price', + $qb->createNamedParameter($priceMin, Connection::PARAM_INT) + ), + $qb->expr()->lt( + 'date.min_price', + $qb->createNamedParameter($priceMax, Connection::PARAM_INT) + ) + )); + } + $qb->andWhere($orX); + } + } + + protected function addDurationFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Duration + if (0 === $filterSettings['nights']) { + return; + } + $nights = $filterSettings['nights']; + if ($nights === FilterService::DURATION_SHORT) { + $qb->andWhere($qb->expr()->lte( + 'date.nights', + $qb->createNamedParameter($nights, Connection::PARAM_INT)) + ); + } elseif ($nights === FilterService::DURATION_LONG) { + $qb->andWhere($qb->expr()->gte( + 'date.nights', + $qb->createNamedParameter($nights, Connection::PARAM_INT)) + ); + } + } + + protected function addHotelTypesFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Hotel types + if (isset($filterSettings['hotelTypes']) && is_array($filterSettings['hotelTypes']) && count($filterSettings['hotelTypes']) > 0) { + $qb->andWhere($qb->expr()->in( + 'date.hotel_type', + $qb->createNamedParameter($filterSettings['hotelTypes'], Connection::PARAM_INT_ARRAY) + )); + } + } + + protected function addBoardTypesFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Board types + if (isset($filterSettings['boardTypes']) && is_array($filterSettings['boardTypes']) && count($filterSettings['boardTypes']) > 0) { + $qb->andWhere($qb->expr()->in( + 'date.board_bus_pro_id', + $qb->createNamedParameter($filterSettings['boardTypes'], Connection::PARAM_INT_ARRAY) + )); + } + } + + protected function addRoomTypesFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Room types + if (isset($filterSettings['roomTypes']) && is_array($filterSettings['roomTypes']) && count($filterSettings['roomTypes']) > 0) { + $qb->andWhere($qb->expr()->in( + 'room.type', + $qb->createNamedParameter($filterSettings['roomTypes'], Connection::PARAM_INT_ARRAY) + )); + } + } + + protected function addTransportationFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Transportation + if (false === empty($filterSettings['bus'])) { + $qb->andWhere($qb->expr()->eq('date.bus_available', 1)); + } + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/CityRepository.php b/packages/ep_products/Classes/Domain/Repository/CityRepository.php new file mode 100644 index 0000000..ba268ec --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/CityRepository.php @@ -0,0 +1,59 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class CityRepository extends AbstractRepository +{ + public function getTeasersByCountryOrRegionAndSeason(int $regionUid = 0, int $countryUid = 0, string $season = 'w', int $storagePid = 0) + { + if ($season === null) { + $season = AbstractDestination::SEASON_WINTER; + } + $query = $this->createQuery(); + $constraints = [ + $query->equals('season', $season), + $query->equals('excludeFromTeasergroups', 0) + ]; + if (0 < $regionUid) { + $constraints[] = $query->equals('region', $regionUid); + } elseif (0 < $countryUid) { + $constraints[] = $query->equals('country', $countryUid); + } + if (0 < $storagePid) { + $constraints[] = $query->equals('pid', $storagePid); + } + + return $query + ->matching($query->logicalAnd($constraints)) + ->execute() + ; + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/ConceptRepository.php b/packages/ep_products/Classes/Domain/Repository/ConceptRepository.php new file mode 100644 index 0000000..31c17f4 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/ConceptRepository.php @@ -0,0 +1,31 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class ConceptRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Repository/ContingentRepository.php b/packages/ep_products/Classes/Domain/Repository/ContingentRepository.php new file mode 100644 index 0000000..527df86 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/ContingentRepository.php @@ -0,0 +1,172 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use CalendR\Event\EventInterface; +use CalendR\Event\Provider\ProviderInterface; +use Doctrine\DBAL\DBALException; +use EP\EpProducts\Domain\Model\Contingent; +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class ContingentRepository extends AbstractRepository implements ProviderInterface +{ + /** + * @return EventInterface[] + * + * @throws DBALException + */ + public function getEvents(\DateTime $begin, ?\DateTime $end = null, array $options = []): array + { + $optionsResolver = new OptionsResolver(); + $optionsResolver->setRequired(['hotel']); + $optionsResolver->setAllowedTypes('hotel', Hotel::class); + $resolvedOptions = $optionsResolver->resolve($options); + + /** @var Hotel $hotel */ + $hotel = $resolvedOptions['hotel']; + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select('c.status as status', 'c.date as date', 'c.min_price as minPrice', 'c.pax', 'c.available', 'c.room_code') + ->from('tx_epproducts_domain_model_contingent', 'c') + ->where('c.hotel = :hotelUid') + ->andWhere('c.date >= :dateFrom') + ->setParameter('hotelUid', $hotel->getUid()) + ->setParameter('dateFrom', $begin->format('Y-m-d')) + ->orderBy('c.date') + ; + + if (null !== $end) { + $qb + ->andWhere('date <= :dateTo') + ->setParameter('dateTo', $end->format('Y-m-d')) + ; + } + + $result = $qb->execute()->fetchAllAssociative(); + + $dates = []; + + foreach ($result as $row) { + if (!\array_key_exists($row['date'], $dates)) { + $dates[$row['date']] = [ + 'status' => Contingent::STATUS_OK, + 'date' => $row['date'], + 'pax' => 0, + 'available' => 0, + ]; + } + if (Contingent::STATUS_OK !== $row['status']) { + $dates[$row['date']]['status'] = $row['status']; + } + if ('BelKal' !== $row['room_code']) { + $dates[$row['date']]['pax'] += $row['pax']; + $dates[$row['date']]['available'] += $row['available']; + } + } + + $events = []; + + foreach ($dates as $row) { + $row['hotel'] = $hotel; + $events[] = Contingent::fromArray($row); + } + + return $events; + } + + /** + * @return array + * + * @throws DBALException + */ + public function getAvailableContingents(Hotel $hotel, Product $product) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select('c.date as date', 'c.min_nights as minNights') + ->addSelectLiteral('SUM(c.available) as total') + ->addSelectLiteral('SUM(c.pax) as capacity') + ->from('tx_epproducts_domain_model_contingent', 'c') + ->innerJoin('c', 'tx_epproducts_domain_model_date', 'd', 'c.date = d.date_start AND c.hotel_code = d.hotel_code') + ->where('c.hotel = :hotelUid') + ->andWhere('d.product = :productUid') + ->andWhere('c.available > 0') + ->andWhere('c.min_price > 0') + ->setParameters([ + 'hotelUid' => $hotel->getUid(), + 'productUid' => $product->getUid(), + ]) + ->groupBy('c.date') + ->orderBy('c.date') + ; + + return $qb->execute()->fetchAllAssociative(); + } + + /** + * @return array + * + * @throws DBALException + */ + public function getAvailableRooms(string $dateFrom, string $dateTo, Hotel $hotel, Product $product) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select('c.date as date', 'c.room_code as code', 'c.room_label as label', 'c.available as available', + 'c.min_price as price', 'c.hotel_bus_pro_id as hotelBusProId', 'c.min_nights as minNights', + 'c.additional_night_min_price as additionalNightPrice', + 'c.additional_night_min_nights as additionalNightMinNights', 'd.bus_pro_id as busProId', + 'd.services_included as servicesIncluded', 'd.services_general as servicesOptional', + 'd.skipass_included as skiPassIncluded', 'd.season as season') + ->from('tx_epproducts_domain_model_contingent', 'c') + ->innerJoin('c', 'tx_epproducts_domain_model_date', 'd', 'c.date = d.date_start AND c.hotel_code = d.hotel_code') + ->where('c.hotel = :hotelUid') + ->andWhere('d.product = :productUid') + ->andWhere('c.date >= :dateFrom') + ->andWhere('c.date <= :dateTo') + ->andWhere('c.min_price > 0') + ->setParameters([ + 'hotelUid' => $hotel->getUid(), + 'productUid' => $product->getUid(), + 'dateFrom' => $dateFrom, + 'dateTo' => $dateTo, + ]) + ->groupBy('c.room_code') + ->orderBy('c.pax') + ->addOrderBy('c.min_price') + ; + + return $qb->execute()->fetchAllAssociative(); + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/CountryRepository.php b/packages/ep_products/Classes/Domain/Repository/CountryRepository.php new file mode 100644 index 0000000..ec29a51 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/CountryRepository.php @@ -0,0 +1,31 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class CountryRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Repository/DateRepository.php b/packages/ep_products/Classes/Domain/Repository/DateRepository.php new file mode 100644 index 0000000..c047213 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/DateRepository.php @@ -0,0 +1,45 @@ +createQuery(); + + return $query + ->matching($query->logicalAnd([ + $query->equals('product', $product), + $query->equals('dateStart', $date), + ])) + ->execute() + ; + } + + /** + * @return string[] serialized pickups of each upcoming date, latest first + */ + public function findPickupsForProductAndHotel(int $productUid, int $hotelUid): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + return $qb + ->select('date.pickups') + ->from('tx_epproducts_domain_model_date', 'date') + ->where( + $qb->expr()->eq('date.product', $qb->createNamedParameter($productUid, Connection::PARAM_INT)), + $qb->expr()->eq('date.hotel', $qb->createNamedParameter($hotelUid, Connection::PARAM_INT)), + $qb->expr()->eq('date.group_travel', $qb->createNamedParameter(0, Connection::PARAM_INT)), + 'date.date_start >= NOW()' + ) + ->orderBy('date.date_start', 'DESC') + ->addOrderBy('date.uid', 'DESC') + ->execute() + ->fetchFirstColumn() + ; + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/FaqRepository.php b/packages/ep_products/Classes/Domain/Repository/FaqRepository.php new file mode 100644 index 0000000..ffd7e7a --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/FaqRepository.php @@ -0,0 +1,62 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Faq; +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper; + +class FaqRepository extends AbstractRepository +{ + + /** + * @param string $uids + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function findByUids($uids) + { + /** @var DataMapper $dataMapper */ + $dataMapper = $this->objectManager->get(DataMapper::class); + $qb = $this->getDbConnection()->createQueryBuilder(); + + $rows = $qb + ->select('*') + ->from('tx_epproducts_domain_model_faq') + ->where('uid IN (:uids)') + ->add('orderBy', 'FIELD(uid, ' . $uids . ')') + ->setParameter('uids', GeneralUtility::trimExplode(',', $uids), Connection::PARAM_INT_ARRAY) + ->execute() + ->fetchAllAssociative() + ; + + return $dataMapper->map(Faq::class, $rows); + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/GroupsPriceOptionRepository.php b/packages/ep_products/Classes/Domain/Repository/GroupsPriceOptionRepository.php new file mode 100644 index 0000000..0148617 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/GroupsPriceOptionRepository.php @@ -0,0 +1,56 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; +use TYPO3\CMS\Extbase\Persistence\Repository; + +class GroupsPriceOptionRepository extends Repository +{ + /** + * @param array $uids + * @return array + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + public function findByUids(array $uids) + { + if (0 === count($uids)) { + return []; + } + + /** @var Typo3QuerySettings $querySettings */ + $querySettings = $this->objectManager->get(Typo3QuerySettings::class); + $querySettings->setRespectStoragePage(false); + $this->setDefaultQuerySettings($querySettings); + + $query = $this->createQuery(); + $query->matching($query->in('uid', $uids)); + + return $query->execute()->toArray(); + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/HotelRepository.php b/packages/ep_products/Classes/Domain/Repository/HotelRepository.php new file mode 100644 index 0000000..0f4972d --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/HotelRepository.php @@ -0,0 +1,272 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use Symfony\Component\OptionsResolver\OptionsResolver; +use TYPO3\CMS\Core\Database\Connection; + +class HotelRepository extends AbstractRepository +{ + + /** + * @param int $hotelUid + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getSingleByUid($hotelUid) + { + $qb = $this->getQueryBuilder(); + $qb + ->andWhere('date.hotel = :hotelUid') + ->orderBy('date.min_price') + ->setParameter('hotelUid', $hotelUid) + ->setMaxResults(1) + ; + + return $qb->execute()->fetchAssociative(); + } + + /** + * @param int $hotelUid + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getDateIndependentSingleByUid($hotelUid) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'hotel.uid as hotelUid', 'hotel.name as hotelName', 'hotel.teaser as hotelTeaser', + 'hotel.category as hotelCategory', 'hotel.detail_page as hotelDetailPage', + 'country.code as countryCode', 'country.name as country', + 'region.name as region' + ) + ->from('tx_epproducts_domain_model_hotel', 'hotel') + ->leftJoin('hotel', 'tx_epproducts_domain_model_country', 'country', 'hotel.country = country.uid') + ->leftJoin('hotel', 'tx_epproducts_domain_model_region', 'region', 'hotel.region = region.uid') + ->where('hotel.uid = :hotelUid') + ->andWhere('hotel.show_as_teaser = 1') + ->setParameter('hotelUid', $hotelUid) + ; + + return $qb->execute()->fetchAssociative(); + } + + /** + * @param int $productUid + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getListByProduct($productUid) + { + $qb = $this->getQueryBuilder(false); + $qb + ->andWhere('date.product = :productUid') + ->setParameter('productUid', $productUid) + ; + + return $qb->execute()->fetchAllAssociative(); + } + + /** + * @param array $criteria + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getListByCriteria(array $criteria) + { + $optionsResolver = new OptionsResolver(); + $optionsResolver->setDefined(['regionUid', 'countryUid', 'conceptUid', 'productUid', 'category', 'type']); + $resolvedCriteria = $optionsResolver->resolve($criteria); + + $qb = $this->getQueryBuilder(); + if ($resolvedCriteria['regionUid']) { + $qb + ->andWhere('date.region = :regionUid') + ->setParameter('regionUid', $resolvedCriteria['regionUid']) + ; + } + if ($resolvedCriteria['countryUid']) { + $qb + ->andWhere('date.country = :countryUid') + ->setParameter('countryUid', $resolvedCriteria['countryUid']) + ; + } + if ($resolvedCriteria['conceptUid']) { + $qb + ->andWhere('date.concept = :conceptUid') + ->setParameter('conceptUid', $resolvedCriteria['conceptUid']) + ; + } + if ($resolvedCriteria['productUid']) { + $qb + ->andWhere('date.product = :productUid') + ->setParameter('productUid', $resolvedCriteria['productUid']) + ; + } + if ($resolvedCriteria['category']) { + $qb + ->andWhere('date.hotel_category = :category') + ->setParameter('category', $resolvedCriteria['category']) + ; + } + if ($resolvedCriteria['type']) { + $qb + ->andWhere('date.hotel_type = :type') + ->setParameter('type', $resolvedCriteria['type']) + ; + } + + return $qb->execute()->fetchAllAssociative(); + } + + /** + * @param array $criteria + * @param array $excludedHotelUids + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getDateIndependentListByCriteria(array $criteria, array $excludedHotelUids = []) + { + $optionsResolver = new OptionsResolver(); + $optionsResolver->setDefined(['regionUid', 'countryUid', 'conceptUid', 'productUid', 'category', 'type']); + $resolvedCriteria = $optionsResolver->resolve($criteria); + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'hotel.uid as hotelUid', 'hotel.name as hotelName', 'hotel.teaser as hotelTeaser', + 'hotel.category as hotelCategory', 'hotel.detail_page as hotelDetailPage', + 'country.code as countryCode', 'country.name as country', + 'region.name as region' + ) + ->from('tx_epproducts_domain_model_hotel', 'hotel') + ->leftJoin('hotel', 'tx_epproducts_domain_model_country', 'country', 'hotel.country = country.uid') + ->leftJoin('hotel', 'tx_epproducts_domain_model_region', 'region', 'hotel.region = region.uid') + ->where('hotel.show_as_teaser = 1') + ->add('orderBy', 'FIELD(hotel.type, ' . Hotel::TYPE_SPORTS_CLUB . ') DESC') + ->addOrderBy('hotel.name', 'ASC') + ; + + if ($excludedHotelUids) { + $qb + ->andWhere('hotel.uid NOT IN (:excludedHotelUids)') + ->setParameter('excludedHotelUids', $excludedHotelUids, Connection::PARAM_INT_ARRAY) + ; + } + if ($resolvedCriteria['productUid']) { + $qb + ->innerJoin('hotel', 'tx_epproducts_product_hotel_mm', 'mm', 'hotel.uid = mm.uid_foreign') + ->andWhere('mm.uid_local = :product') + ->setParameter('product', $resolvedCriteria['productUid']) + ; + } + if ($resolvedCriteria['countryUid']) { + $qb + ->andWhere('hotel.country = :country') + ->setParameter('country', $resolvedCriteria['countryUid']) + ; + } + if ($resolvedCriteria['regionUid']) { + $qb + ->andWhere('hotel.region = :region') + ->setParameter('region', $resolvedCriteria['regionUid']) + ; + } + if ($resolvedCriteria['category']) { + $qb + ->andWhere('hotel.category = :category') + ->setParameter('category', $resolvedCriteria['category']) + ; + } + if ($resolvedCriteria['type']) { + $qb + ->andWhere('hotel.type = :type') + ->setParameter('type', $resolvedCriteria['type']) + ; + } + + return $qb->execute()->fetchAllAssociative(); + } + + /** + * @param bool $excludeNonSearchable + * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder + * @throws \Doctrine\DBAL\DBALException + */ + protected function getQueryBuilder($excludeNonSearchable = true) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.hotel as hotelUid', 'date.hotel_name as hotelName', 'date.hotel_teaser as hotelTeaser', + 'date.hotel_category as hotelCategory', 'date.hotel_detail_page as hotelDetailPage', + 'date.date_start as dateStart', 'date.date_end as dateEnd', 'date.earlybird as earlybird', + 'date.new as new', 'date.low_contingent as lowContingent', 'date.board as board', + 'date.product_fact as productFact', 'date.hotel_fact as hotelFact', 'date.region_fact as regionFact', + 'date.services_included as servicesIncluded', 'date.min_price as minPrice', + 'date.pseudo_price as pseudoPrice', 'date.nights as nights', 'date.available as available', + 'date.country as country', 'date.country_code as countryCode', 'date.region as region' + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->leftJoin('date', 'tx_epproducts_product_region_mm', 'mm', 'mm.uid_local = date.product') + ->addGroupBy('date.hotel') + ->addGroupBy('date.date_start') + ->addGroupBy('date.nights') + ->add('orderBy', 'FIELD(date.hotel_type, ' . Hotel::TYPE_SPORTS_CLUB . ') DESC') + ; + + if ($excludeNonSearchable) { + $qb->where('date.product_searchable = 1'); + } + + return $qb; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + public function getMapList() + { + $query = $this->createQuery(); + $query->matching( + $query->logicalAnd( + $query->greaterThan('latitude', 0), + $query->greaterThan('longitude', 0) + ) + ); + + return $query->execute(); + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/InfoboxRepository.php b/packages/ep_products/Classes/Domain/Repository/InfoboxRepository.php new file mode 100644 index 0000000..dbe54fe --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/InfoboxRepository.php @@ -0,0 +1,21 @@ +createQuery(); + $query->getQuerySettings()->setRespectStoragePage(false); + + $result = $query + ->matching($query->equals('defaultText', true)) + ->execute() + ; + + return $result->getFirst(); + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/MatchCodeRepository.php b/packages/ep_products/Classes/Domain/Repository/MatchCodeRepository.php new file mode 100644 index 0000000..a5a4427 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/MatchCodeRepository.php @@ -0,0 +1,31 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class MatchCodeRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Repository/ProductRepository.php b/packages/ep_products/Classes/Domain/Repository/ProductRepository.php new file mode 100644 index 0000000..9a1007a --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/ProductRepository.php @@ -0,0 +1,414 @@ +getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.concept as conceptUid', 'date.concept_name as conceptName', + 'date.concept_code as conceptCode', 'con.popup_text as conceptPopupText', + 'date.product as productUid', 'date.product_name as productName', + 'date.product_detail_page as productDetailPage', 'date.product_feature as productFeature', + 'date.country as countryUid','date.country_name as countryName', 'date.country_code as countryCode', + 'date.region as regionUid', 'date.region_name as regionName', 'date.region_feature as regionFeature', + 'date.date_start as dateStart', 'date.date_end as dateEnd', + 'date.hotel as hotelUid', 'date.hotel_name as hotelName', 'date.hotel_category as hotelCategory', + 'date.earlybird as earlybird', 'date.new as new', 'date.low_contingent as lowContingent', + 'date.board as board', 'date.product_fact as productFact', 'date.hotel_fact as hotelFact', + 'date.region_fact as regionFact', 'date.services_included as servicesIncluded', + 'date.skipass_included as skiPassIncluded', 'date.min_price as minPrice', 'date.nights as nights', + 'date.available as available', 'date.pseudo_price as pseudoPrice' + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->leftJoin('date', 'tx_epproducts_domain_model_concept', 'con', 'date.concept = con.uid') + ->where($qb->expr()->in( + 'date.product', + $qb->createNamedParameter($productUids, Connection::PARAM_INT_ARRAY)) + ) + ->andWhere($qb->expr()->in( + 'date.hotel', + $qb->createNamedParameter($hotelUids, Connection::PARAM_INT_ARRAY)) + ) + ->andWhere($qb->expr()->eq('date.product_searchable', 1)) + ->orderBy('date.concept_sorting') + ->addOrderBy('date.product_name') + ->addOrderBy('date.min_price') + ->addOrderBy('date.date_start') + ->addOrderBy('date.hotel_name') + ; + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function getSearchResult(array $filterSettings, array $excludedConceptUids = []): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.concept as conceptUid', 'date.concept_name as conceptName', + 'date.concept_code as conceptCode', 'date.concept_sorting as conceptSorting', + 'con.popup_text as conceptPopupText', + 'date.product as productUid', 'date.product_name as productName', + 'date.product_detail_page as productDetailPage', 'date.product_feature as productFeature', + 'date.daytrip as daytrip', 'date.country as countryUid','date.country_name as countryName', + 'date.country_code as countryCode', 'date.region as regionUid', 'date.region_name as regionName', + 'date.region_feature as regionFeature', 'date.date_start as dateStart', 'date.date_end as dateEnd', + 'date.hotel as hotelUid', 'date.hotel_name as hotelName', 'date.hotel_category as hotelCategory', + 'date.hotel_type as hotelType', 'date.season as season', + 'date.earlybird as earlybird', 'date.new as new', 'date.low_contingent as lowContingent', + 'date.board as board', 'date.board_bus_pro_id as boardUid', + 'date.product_fact as productFact', 'date.hotel_fact as hotelFact', 'date.region_fact as regionFact', + 'date.services_included as servicesIncluded', 'date.skipass_included as skiPassIncluded', + 'date.bus_available as busAvailable', 'date.new as new', + 'date.min_price as minPrice', 'date.nights as nights', 'date.available as available', + 'date.pseudo_price as pseudoPrice', 'date.hide_booking_button as hideBookingButton' + ) + // The GROUP BY below collapses many room rows per group; an arbitrary + // room.type would hide most types from the filter facet. + ->addSelectLiteral('GROUP_CONCAT(DISTINCT room.type) as roomTypes') + ->from('tx_epproducts_domain_model_date', 'date') + ->leftJoin('date', 'tx_epproducts_domain_model_concept', 'con', 'date.concept = con.uid') + ->leftJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid') + ->where($qb->expr()->andX( + $qb->expr()->eq('date.product_searchable', 1), + $qb->expr()->gt('date.available', 0), + 'date.date_start >= NOW()' + )) + ->groupBy('date.product') + ->addGroupBy('date.hotel') + ->addGroupBy('date.date_start') + ->addGroupBy('date.nights') + ->orderBy('date.concept_sorting') + ->addOrderBy('important', 'DESC') + ->addOrderBy('date.product_name') + ->addOrderBy('date.min_price') + ->addOrderBy('date.date_start') + ->addOrderBy('date.hotel_name') + ; + + if (count($excludedConceptUids) > 0) { + $qb->andWhere($qb->expr()->notIn( + 'date.concept', + $qb->createNamedParameter($excludedConceptUids, Connection::PARAM_INT_ARRAY)) + ); + } + + if ($filterSettings['productsPid']) { + $productsPid = $filterSettings['productsPid']; + $qb->andWhere($qb->expr()->in( + 'date.product_pid', + $qb->createNamedParameter($productsPid, Connection::PARAM_INT_ARRAY)) + ); + } + + $this->addFiltersettingsConditions($qb, $filterSettings); + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function getTeasers(array $filterSettings, array $excludedConceptUids = [], int $limit = 0): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.product as productUid', 'date.product_name as productName', + 'date.product_detail_page as productDetailPage', 'date.product_feature as productFeature', + 'date.product_teaser as productTeaser', 'date.product_fact as productFact', + 'date.product_slug as productSlug', + 'date.hotel as hotelUid', 'date.hotel_fact as hotelFact', 'date.hotel_name as hotelName', + 'date.hotel_category as hotelCategory', 'date.region_fact as regionFact', + 'date.region_name as regionName', 'date.region_feature as regionFeature', + 'date.country_name as countryName', 'date.country_code as countryCode', + 'date.date_start as dateStart', 'date.date_end as dateEnd', 'date.uid as dateUid', + 'date.min_price as minPrice', 'date.pseudo_price as pseudoPrice', + 'date.nights as nights', 'date.available as available', 'date.season as season', + 'date.skipass_included as skiPassIncluded', 'date.bus_included as busIncluded', + 'date.earlybird', 'date.new', 'date.daytrip', 'date.hide_booking_button as hideBookingButton', + 'date.low_contingent as lowContingent', 'date.concept as conceptUid', + 'date.concept_code as conceptCode', 'date.concept_name as conceptName', + 'con.popup_text as conceptPopupText', 'date.board' + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->leftJoin('date', 'tx_epproducts_domain_model_concept', 'con', 'date.concept = con.uid') + ->where('date.date_start >= NOW()') + ->groupBy('date.product') + ->addGroupBy('date.hotel') + ->addGroupBy('date.date_start') + ->addGroupBy('date.nights') + ->having('date.available > 0') + ->orderBy('date.important', 'DESC') + ->addOrderBy('date.new', 'DESC') + ->addOrderBy('date.concept_sorting') + ->addOrderBy('date.date_start') + ; + + if ($filterSettings['productsPid']) { + $productsPid = (array) $filterSettings['productsPid']; + $qb->andWhere($qb->expr()->in( + 'date.product_pid', + $qb->createNamedParameter($productsPid, Connection::PARAM_INT_ARRAY)) + ); + } + + if ($limit > 0) { + $qb->setMaxResults($limit); + } + + if (empty($filterSettings['productUid'])) { + $qb->andWhere($qb->expr()->eq('date.product_searchable', 1)); + } else { + $qb->andWhere($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($filterSettings['productUid'], Connection::PARAM_INT)) + ); + } + + $this->addDateFilterConditions($qb, $filterSettings); + $this->addHotelFilterConditions($qb, $filterSettings); + $this->addCountryRegionAndCityFilterConditions($qb, $filterSettings); + $this->addConceptFilterConditions($qb, $filterSettings); + $this->addTransportationFilterConditions($qb, $filterSettings); + + if (count($excludedConceptUids) > 0) { + $qb->andWhere($qb->expr()->notIn( + 'concept', + $qb->createNamedParameter($excludedConceptUids, Connection::PARAM_INT_ARRAY)) + ); + } + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function getPricetable(Product $product, Hotel $hotel, array $filterSettings, Date $date = null): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.uid as dateUid', 'date.date_start as dateStart', 'date.date_end as dateEnd', + 'date.bus_pro_id as dateBusProId', 'date.hotel as hotelUid', 'date.hotel_bus_pro_id as hotelBusProId', + 'date.bus_included as dateBusIncluded', 'date.skipass_included as dateSkiPassIncluded', + 'date.group_travel as dateGroupTravel', + 'date.season as season', 'date.product_detail_page as productDetailPage', + 'room.uid as roomUid', 'room.bus_pro_id as roomBusProId', 'room.name as roomName', + 'room.price as roomPrice', 'date.discount as roomDiscount', 'date.bus_price as busPrice', + 'room.services as roomOptionalServices', 'room.nights as roomNights', 'room.available as roomAvailable', + 'room.status as roomStatus' + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'date.uid = room.date') + ->orderBy('room.pax', 'ASC') + ->addOrderBy('room.price', 'ASC') + ->addOrderBy('room.available', 'DESC') + ; + + $qb + ->where('date.date_start >= NOW()') + ->andWhere($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($product->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->eq( + 'date.hotel', + $qb->createNamedParameter($hotel->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->neq( + 'room.status', + $qb->createNamedParameter(Room::ROOM_STATUS_FULLY_BOOKED, Connection::PARAM_INT)) + ) + ; + + if ($filterSettings['productsPid']) { + $productsPid = (array) $filterSettings['productsPid']; + $qb->andWhere($qb->expr()->in( + 'date.product_pid', + $qb->createNamedParameter($productsPid, Connection::PARAM_INT_ARRAY)) + ); + } + + if ($date !== null) { + $qb->andWhere($qb->expr()->eq( + 'date.uid', + $qb->createNamedParameter($date->getUid(), Connection::PARAM_INT)) + ); + } + + $this->addFiltersettingsConditions($qb, $filterSettings); + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function getAvailableDates(Product $product, Hotel $hotel, array $filterSettings): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.uid as dateUid', 'date.date_start as dateStart', 'date.date_end as dateEnd', + 'date.season as season', 'date.min_price as dateMinPrice', 'date.pseudo_price as datePseudoPrice', + 'date.discount as dateDiscount', 'date.bus_price as busPrice', 'date.nights as dateNights', + 'date.bus_included as dateBusIncluded', 'date.services_included as dateServicesIncluded', + 'date.services_general as dateServicesGeneral', 'date.skipass_included as dateSkiPassIncluded', + 'date.group_travel as dateGroupTravel', + 'date.bus_pro_id as dateBusProId', 'date.hotel_bus_pro_id as hotelBusProId', + 'date.available as available', 'date.product_detail_page as productDetailPage', + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid') + ->orderBy('date.date_start', 'ASC') + ->addOrderBy('date.min_price', 'ASC') + ->where('date.date_start >= NOW()') + ->andWhere($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($product->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->eq( + 'date.hotel', + $qb->createNamedParameter($hotel->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->neq( + 'room.status', + $qb->createNamedParameter(Room::ROOM_STATUS_FULLY_BOOKED, Connection::PARAM_INT)) + ) + ->groupBy('date.date_start') + ; + + if ($filterSettings['productsPid']) { + $productsPid = (array) $filterSettings['productsPid']; + $qb->andWhere($qb->expr()->in( + 'date.product_pid', + $qb->createNamedParameter($productsPid, Connection::PARAM_INT_ARRAY)) + ); + } + + $this->addFiltersettingsConditions($qb, $filterSettings, true); + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function hasDates(Product $product): bool + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('date.uid') + ->from('tx_epproducts_domain_model_date', 'date') + ->where($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($product->getUid(), Connection::PARAM_INT)) + ) + ->setMaxResults(1) + ->execute() + ->fetchOne() + ; + + return $result !== false; + } + + public function getDatesForExport(Product $product, Hotel $hotel): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.bus_pro_id as dateBusProId', 'date.date_start as dateStart', 'date.date_end as dateEnd', + 'date.min_price as dateMinPrice', 'date.discount as dateDiscount', 'date.bus_price as busPrice', + 'date.nights as dateNights', 'date.bus_included as dateBusIncluded', 'date.bus_price as busPrice', + 'date.services_included as dateServicesIncluded', 'date.services_general as dateServicesGeneral', + 'date.skipass_included as dateSkiPassIncluded', 'date.bus_pro_id as dateBusProId', + 'date.discount as roomDiscount', 'date.hotel_bus_pro_id as hotelBusProId', 'date.pickups as pickups', + 'room.uid as roomUid', 'room.bus_pro_id as roomBusProId', 'room.code as roomCode', + 'room.name as roomName', 'room.price as roomPrice', 'room.pax as roomPax', + 'room.services as roomOptionalServices', 'room.nights as roomNights', 'room.available as roomAvailable' + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid') + ->orderBy('date.date_start', 'ASC') + ->addOrderBy('date.min_price', 'ASC') + ->where('date.date_start >= NOW()') + ->andWhere($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($product->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->eq( + 'date.hotel', + $qb->createNamedParameter($hotel->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->neq( + 'room.status', + $qb->createNamedParameter(Room::ROOM_STATUS_FULLY_BOOKED, Connection::PARAM_INT)) + ) + ; + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function getEventPriceTable(Product $product): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.bus_pro_id as dateBusProId', 'date.hotel_bus_pro_id as hotelBusProId', + 'date.date_end as dateEnd', 'date.hotel as hotelUid', 'date.hotel_name as hotelName', + 'date.hotel_header_title as hotelTitle', 'date.hotel_category as hotelCategory', + 'room.name as roomName', 'room.pax as roomPax', 'room.price as roomPrice', + 'room.available as available', + 'hotel.detail_page as hotelDetailPageUid', 'hotel.short_name as hotelShortName', + 'hotel.show_as_teaser as hotelShowAsTeaser', + 'hotel_mapping.detail_page as hotelMappingDetailPageUid', + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid') + ->innerJoin('date', 'tx_epproducts_domain_model_hotel', 'hotel', 'hotel.uid = date.hotel') + ->leftJoin('hotel', 'tx_epproducts_domain_model_hotel_mapping', 'hotel_mapping', 'hotel_mapping.product = date.product AND hotel_mapping.hotel = date.hotel') + ->where($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($product->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->neq( + 'room.status', + $qb->createNamedParameter(Room::ROOM_STATUS_FULLY_BOOKED, Connection::PARAM_INT)) + ) + ->orderBy('room.available', 'DESC') + ->addOrderBy('room.name', 'ASC') + ; + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/RegionRepository.php b/packages/ep_products/Classes/Domain/Repository/RegionRepository.php new file mode 100644 index 0000000..5f3baa6 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/RegionRepository.php @@ -0,0 +1,56 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class RegionRepository extends AbstractRepository +{ + public function getTeasersByCountryAndSeason(int $countryUid, string $season = 'w', int $storagePid = 0) + { + if ($season === null) { + $season = AbstractDestination::SEASON_WINTER; + } + $query = $this->createQuery(); + $constraints = [ + $query->equals('country', $countryUid), + $query->equals('season', $season), + $query->equals('excludeFromTeasergroups', 0) + ]; + + if (0 < $storagePid) { + $constraints[] = $query->equals('pid', $storagePid); + } + + return $query + ->matching($query->logicalAnd($constraints)) + ->execute() + ; + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/ResellerRepository.php b/packages/ep_products/Classes/Domain/Repository/ResellerRepository.php new file mode 100644 index 0000000..eb02598 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/ResellerRepository.php @@ -0,0 +1,31 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class ResellerRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Repository/RoomRepository.php b/packages/ep_products/Classes/Domain/Repository/RoomRepository.php new file mode 100644 index 0000000..90a8e36 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/RoomRepository.php @@ -0,0 +1,31 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class RoomRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Repository/SnowreportRepository.php b/packages/ep_products/Classes/Domain/Repository/SnowreportRepository.php new file mode 100644 index 0000000..bde5d50 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/SnowreportRepository.php @@ -0,0 +1,71 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Database\Connection; + +class SnowreportRepository extends AbstractRepository +{ + + /** + * @param array $regionUids + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getSnowreportsForRegions(array $regionUids = null) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $query = $qb + ->select( + 'snowreport.*', + 'region.name as regionName', 'region.uid as regionUid', 'region.detail_page as regionDetailPage', + 'country.name as countryName', 'country.uid as countryUid', 'country.code as countryCode', + 'country.detail_page as countryDetailPage', + 'season_start as seasonStart', 'season_end as seasonEnd' + ) + ->addSelectLiteral('(UNIX_TIMESTAMP() >= snowreport.season_start AND UNIX_TIMESTAMP() <= snowreport.season_end) as inSeason') + ->from('tx_epproducts_domain_model_snowreport', 'snowreport') + ->innerJoin('snowreport', 'tx_epproducts_domain_model_region', 'region', 'snowreport.uid = region.snowreport') + ->innerJoin('region', 'tx_epproducts_domain_model_country', 'country', 'region.country = country.uid') + ->where('region.snowreport > 0') + ->groupBy('snowreport.vendor_uid') + ->orderBy('region.name') + ; + + if ($regionUids) { + $qb + ->andWhere('region.uid IN (:regionUids)') + ->setParameter('regionUids', $regionUids, Connection::PARAM_INT_ARRAY) + ; + } + + return $query->execute()->fetchAllAssociative(); + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php b/packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php new file mode 100644 index 0000000..9e64a20 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php @@ -0,0 +1,52 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class TeammemberRepository extends AbstractRepository +{ + /** + * @var array + */ + protected $defaultOrderings = [ + 'sorting' => 'ASC', + ]; + + /** + * @param array $categories + * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + public function findByCategories(array $categories = []) + { + $qb = $this->createQuery(); + + $qb->matching($qb->in('categories.uid', $categories)); + + return $qb->execute(); + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/TravelinfoRepository.php b/packages/ep_products/Classes/Domain/Repository/TravelinfoRepository.php new file mode 100644 index 0000000..f62f188 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/TravelinfoRepository.php @@ -0,0 +1,6 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class WebcamRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Serialization/City.php b/packages/ep_products/Classes/Domain/Serialization/City.php new file mode 100644 index 0000000..b75f87d --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/City.php @@ -0,0 +1,57 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Fact; +use EP\EpProducts\Utility\SerializationUtility; + +class City +{ + /** + * @param \EP\EpProducts\Domain\Model\City $entity + * @return array + */ + public static function fromEntity(\EP\EpProducts\Domain\Model\City $entity) + { + $city = []; + + $city['@id'] = $entity->getUid(); + $city['name'] = $entity->getName(); + $city['description'] = SerializationUtility::sanitizeText($entity->getDescription()); + $city['party'] = SerializationUtility::sanitizeText($entity->getParty()); + $city['leisure'] = SerializationUtility::sanitizeText($entity->getLeisure()); + $city['facts'] = []; + $city['facts']['fact'] = array_map(function ($fact) { + /** @var Fact $fact */ + return htmlspecialchars($fact->getName()); + }, $entity->getFacts()->toArray()); + + return $city; + } + +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Date.php b/packages/ep_products/Classes/Domain/Serialization/Date.php new file mode 100644 index 0000000..05270a2 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Date.php @@ -0,0 +1,63 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Date +{ + /** + * @param array $array + * @return array + */ + public static function fromArray(array $array) + { + $date = [ + 'servicesincluded' => [], + 'servicesoptional' => [], + 'rooms' => [], + ]; + + $date['@id'] = $array['dateBusProId']; + $date['from'] = $array['dateStart']; + $date['to'] = $array['dateEnd']; + $date['minprice'] = (float) $array['dateMinPrice']; + $date['nights'] = $array['dateNights']; + $date['skipassincluded'] = $array['dateSkiPassIncluded']; + $date['busincluded'] = $array['dateBusIncluded']; + $date['servicesincluded']['service'] = unserialize($array['dateServicesIncluded']); + $servicesGeneral = unserialize($array['dateServicesGeneral']); + $date['servicesoptional'] = Services::fromArray($servicesGeneral); + $date['pickups'] = []; + foreach (unserialize($array['pickups']) as $row) + { + $date['pickups']['pickup'][] = $row; + } + + return $date; + } + +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Hotel.php b/packages/ep_products/Classes/Domain/Serialization/Hotel.php new file mode 100644 index 0000000..9de9509 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Hotel.php @@ -0,0 +1,56 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Hotel +{ + /** + * @param \EP\EpProducts\Domain\Model\Hotel $entity + * @return array + */ + public static function fromEntity(\EP\EpProducts\Domain\Model\Hotel $entity) + { + $hotel = [ + 'dates' => [], + 'images' => [], + ]; + + $hotel['@id'] = $entity->getUid(); + $hotel['@code'] = $entity->getCode(); + $hotel['name'] = $entity->getName(); + $hotel['category'] = \EP\EpProducts\Domain\Model\Hotel::$categoryLabels[$entity->getCategory()]; + $hotel['type'] = \EP\EpProducts\Domain\Model\Hotel::$typeLabels[$entity->getType()]; + $hotel['description'] = SerializationUtility::sanitizeText($entity->getDescription()); + $hotel['roomtypes'] = SerializationUtility::sanitizeText($entity->getRoomTypes()); + $hotel['features'] = SerializationUtility::sanitizeText($entity->getFeatures()); + $hotel['images']['image'] = SerializationUtility::preprocessImages($entity->getResellerImages()); + + return $hotel; + } +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Product.php b/packages/ep_products/Classes/Domain/Serialization/Product.php new file mode 100644 index 0000000..864b022 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Product.php @@ -0,0 +1,104 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Fact; +use EP\EpProducts\Utility\SerializationUtility; +use TYPO3\CMS\Core\Site\SiteFinder; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class Product +{ + /** + * @param \EP\EpProducts\Domain\Model\Product $entity + * @param array $imageProcessingInstructions + * @param array $queryData + * @return array + */ + public static function fromEntity + ( + \EP\EpProducts\Domain\Model\Product $entity, + array $imageProcessingInstructions = null, + array $queryData = [] + ) + { + $product = [ + 'concept' => [], + 'facts' => [], + 'hotels' => [], + 'images' => [], + ]; + + $product['@id'] = $entity->getBusProId(); + $product['@code'] = $entity->getCode(); + $product['name'] = preg_replace('/[\r\n\s]+/', ' ', $entity->getName()) . ' - ' . $entity->getRegion()->getName(); + + //TODO: Don't hardcode concept uids here, make them configurable + $conceptUid = $entity->getConcept()->getUid(); + if ($conceptUid !== 3 && $conceptUid !== 4) { + $product['name'] .= ' (' . $entity->getConcept()->getName() . ')'; + } + + $product['facts']['fact'] = array_map(function ($fact) { + /** @var Fact $fact */ + return htmlspecialchars($fact->getName()); + }, $entity->getFacts()->toArray()); + + $product['skipass'] = SerializationUtility::sanitizeText($entity->getSkiPassDescription()); + $product['concept']['name'] = $entity->getConcept()->getName(); + $product['concept']['description'] = SerializationUtility::sanitizeText($entity->getConcept()->getDescription()); + $product['board'] = SerializationUtility::sanitizeText($entity->getBoardDescription()); + $product['programme'] = SerializationUtility::sanitizeText($entity->getProgrammeDescription()); + $product['description'] = SerializationUtility::sanitizeText($entity->getTeaser()); + $product['images']['image'] = SerializationUtility::preprocessImages($entity->getResellerImages(), $imageProcessingInstructions); + $product['video'] = $entity->getVideo(); + $product['journey'] = null; + + if ($entity->getJourney()) { + $product['journey'] = [ + 'byBus' => SerializationUtility::sanitizeText($entity->getJourney()->getByBus()), + 'byCar' => SerializationUtility::sanitizeText($entity->getJourney()->getByCar()), + ]; + } + + if ($entity->getExternalLink()) { + $product['link'] = $entity->getExternalLink(); + } else { + /** @var SiteFinder $siteFinder */ + $siteFinder = GeneralUtility::makeInstance(SiteFinder::class); + $site = $siteFinder->getSiteByIdentifier('ep-reisen'); + $product['link'] = $site->getRouter()->generateUri($entity->getDetailPage(), $queryData); + } + + $product['region'] = Region::fromEntity($entity->getRegion()); + $product['city'] = City::fromEntity($entity->getCity()); + + return $product; + } + +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Region.php b/packages/ep_products/Classes/Domain/Serialization/Region.php new file mode 100644 index 0000000..312b74c --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Region.php @@ -0,0 +1,61 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Fact; +use EP\EpProducts\Utility\SerializationUtility; + +class Region +{ + /** + * @param \EP\EpProducts\Domain\Model\Region $entity + * @return array + */ + public static function fromEntity(\EP\EpProducts\Domain\Model\Region $entity) + { + $region = []; + + $region['@id'] = $entity->getUid(); + $region['name'] = $entity->getName(); + $region['description'] = SerializationUtility::sanitizeText($entity->getSkiArea()); + $region['descriptionExtended'] = SerializationUtility::sanitizeText($entity->getSkiAreaExtended()); + $region['piste'] = $entity->getLength(); + $region['height'] = $entity->getHeight(); + $region['lifts'] = $entity->getLifts(); + $region['skipass'] = SerializationUtility::sanitizeText($entity->getSkiPass()); + $region['news'] = SerializationUtility::sanitizeText($entity->getNews()); + $region['facts'] = []; + $region['facts']['fact'] = array_map(function ($fact) { + /** @var Fact $fact */ + return htmlspecialchars($fact->getName()); + }, $entity->getFacts()->toArray()); + + return $region; + } + +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Room.php b/packages/ep_products/Classes/Domain/Serialization/Room.php new file mode 100644 index 0000000..e6d6025 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Room.php @@ -0,0 +1,53 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Room +{ + /** + * @param array $array + * @return array + */ + public static function fromArray(array $array) + { + $room = []; + + $room['@id'] = $array['roomBusProId']; + $room['@pax'] = $array['roomPax']; + $room['@code'] = $array['roomCode']; + $room['name'] = $array['roomName']; + $room['price'] = (float) $array['roomPrice']; + $room['busprice'] = (float) $array['busPrice']; + $room['nights'] = $array['roomNights']; + $room['available'] = $array['roomAvailable']; + + $servicesOptional = unserialize($array['roomOptionalServices']); + $room['servicesoptional'] = Services::fromArray($servicesOptional); + + return $room; + } +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Services.php b/packages/ep_products/Classes/Domain/Serialization/Services.php new file mode 100644 index 0000000..2ee2a8f --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Services.php @@ -0,0 +1,73 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Services +{ + /** + * @var array + */ + protected static $categoryLabels = [ + 'BUS' => 'Anreise', + 'VPF' => 'Verpflegung', + 'SPA' => 'Skipass', + 'KUR' => 'Kurse', + 'VER' => 'Verleih', + 'SON' => 'Sonstiges', + ]; + + /** + * @param array $array + * @return array + */ + public static function fromArray(array $array) + { + $services = [ + 'category' => [], + ]; + + foreach ($array as $categoryId => $categoryData) + { + $category = [ + 'service' => [], + '@label' => self::$categoryLabels[$categoryId], + ]; + foreach ($categoryData as $row) + { + $service = [ + '@id' => $row['idBusPro'], + 'label' => $row['label'], + 'price' => $row['price'], + ]; + $category['service'][] = $service; + } + $services['category'][] = $category; + } + + return $services; + } +} diff --git a/packages/ep_products/Classes/EpTeam/ApiClient.php b/packages/ep_products/Classes/EpTeam/ApiClient.php new file mode 100644 index 0000000..b348592 --- /dev/null +++ b/packages/ep_products/Classes/EpTeam/ApiClient.php @@ -0,0 +1,244 @@ +request('POST', $uri, [ + 'json' => $payload, + ]); + + $statusCode = $this->readStatusCode($response, 'POST', $uri); + + if (false === in_array($statusCode, self::ACCEPTED_STATUS_CODES, true)) { + $message = sprintf( + 'EP Team API returned unexpected status %d for POST %s: %s', + $statusCode, + $uri, + $this->readErrorBody($response) + ); + $this->logWarning($message); + + throw new ApiException($message, $statusCode); + } + + $this->logAcceptedSubmission($response, $payload); + } + + /** + * @throws ApiException + */ + private function request(string $method, string $uri, array $options): ResponseInterface + { + try { + return $this->getHttpClient()->request($method, $uri, $options); + } catch (TransportExceptionInterface $e) { + $message = sprintf('EP Team API transport error for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + + throw new ApiException($message, 0, $e); + } + } + + /** + * Reading the status completes the transfer, so this is also what turns the lazy response + * returned by the client into a finished request. + * + * @throws ApiException + */ + private function readStatusCode(ResponseInterface $response, string $method, string $uri): int + { + try { + return $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + $message = sprintf('EP Team API transport error while reading status for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + + throw new ApiException($message, 0, $e); + } + } + + private function readErrorBody(ResponseInterface $response): string + { + try { + $body = trim($response->getContent(false)); + } catch (TransportExceptionInterface $e) { + return ''; + } + + if ('' === $body) { + return ''; + } + + if (mb_strlen($body) > self::ERROR_BODY_EXCERPT_LENGTH) { + $body = mb_substr($body, 0, self::ERROR_BODY_EXCERPT_LENGTH) . '…'; + } + + return $body; + } + + /** + * The submission uuid the endpoint returns is what makes a support question ("did our + * application arrive?") answerable from the TYPO3 log alone. Reading the body also drains + * the response, which returns the connection to the pool instead of cancelling the transfer. + * Deliberately without any of the submitted values: these payloads are personal data. + */ + private function logAcceptedSubmission(ResponseInterface $response, array $payload): void + { + try { + $data = $response->toArray(false); + } catch (\Throwable $e) { + // A body that is absent (204) or not JSON costs nothing here - the submission is + // stored either way, which the asserted status has already established. + return; + } + + if (null !== $this->logger) { + $this->logger->info('Application form submission accepted by the EP Team API.', [ + 'form' => $payload['form'] ?? null, + 'pageUid' => $payload['pageUid'] ?? null, + 'uuid' => $data['uuid'] ?? null, + ]); + } + } + + /** + * @throws ApiException + */ + private function getHttpClient(): HttpClientInterface + { + if (null !== static::$httpClient) { + return static::$httpClient; + } + + $config = $this->getConfiguration(); + $baseUrl = trim((string)($config['epTeamApiBaseUrl'] ?? '')); + $apiKey = trim((string)($config['epTeamApiKey'] ?? '')); + + if ('' === $baseUrl) { + throw new ApiException('EP Team API base URL is not configured.'); + } + if ('' === $apiKey) { + throw new ApiException('EP Team API key is not configured.'); + } + if (0 !== strpos($baseUrl, 'https://')) { + // The application answers an http request with a 301 to the https URL, and this + // client does not follow redirects, so a plain http base URL fails every submission. + $this->logWarning(sprintf('EP Team API base URL is not https (%s); submissions will fail on the redirect.', $baseUrl)); + } + + $options = [ + 'headers' => [ + 'X-Api-Key' => $apiKey, + ], + ]; + + // Without these the client falls back to PHP's default_socket_timeout (commonly 60s), + // so a stalled API pins a PHP worker for a minute per request - and this call happens + // inside the visitor's form submission, with the visitor waiting for it. The defaults + // live in code rather than in ext_conf_template.txt: ExtensionConfiguration::get() + // returns the stored configuration verbatim and only syncs the template when an + // extension has no configuration at all, so a newly added template key is absent until + // someone saves extension configuration by hand. Falling back to 0 there would silently + // mean "no timeout" - the opposite of what this guard is for. An explicit 0 still + // disables. + $timeout = (int)($config['epTeamApiTimeout'] ?? self::DEFAULT_TIMEOUT); + if ($timeout > 0) { + $options['timeout'] = $timeout; + } + $maxDuration = (int)($config['epTeamApiMaxDuration'] ?? self::DEFAULT_MAX_DURATION); + if ($maxDuration > 0) { + $options['max_duration'] = $maxDuration; + } + + return static::$httpClient = HttpClient::createForBaseUri(rtrim($baseUrl, '/') . '/', $options); + } + + private function getConfiguration(): array + { + if (null === $this->configuration) { + $this->configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); + } + + return $this->configuration; + } + + private function logError(string $message, array $context = []): void + { + if (null !== $this->logger) { + $this->logger->error($message, $context); + } + } + + private function logWarning(string $message, array $context = []): void + { + if (null !== $this->logger) { + $this->logger->warning($message, $context); + } + } +} diff --git a/packages/ep_products/Classes/EpTeam/ApiException.php b/packages/ep_products/Classes/EpTeam/ApiException.php new file mode 100644 index 0000000..e0d7d2c --- /dev/null +++ b/packages/ep_products/Classes/EpTeam/ApiException.php @@ -0,0 +1,7 @@ +readImportTimestamp('fileadmin/products_import.info'); + $contingentsDataUpdatedTimestamp = $timestampService->readImportTimestamp('fileadmin/contingents_import.info'); + + $event->getToolbarItem()->addSystemInformation( + 'Letzer Produktdaten-Import', + $productDataUpdatedTimestamp ? $productDataUpdatedTimestamp->format('d.m.y H:i:s') : '-', + 'ep-logo' + ); + $event->getToolbarItem()->addSystemInformation( + 'Letzer Kontingente Import', + $contingentsDataUpdatedTimestamp ? $contingentsDataUpdatedTimestamp->format('d.m.y H:i:s') : '-', + 'ep-logo' + ); + } +} diff --git a/packages/ep_products/Classes/Hooks/PageLayoutView.php b/packages/ep_products/Classes/Hooks/PageLayoutView.php new file mode 100644 index 0000000..43fbf4e --- /dev/null +++ b/packages/ep_products/Classes/Hooks/PageLayoutView.php @@ -0,0 +1,258 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Backend\View\PageLayoutView as CorePageLayoutView; +use TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface; +use TYPO3\CMS\Core\Service\FlexFormService; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class PageLayoutView implements PageLayoutViewDrawItemHookInterface +{ + + /** + * @param CorePageLayoutView $parentObject + * @param bool $drawItem + * @param string $headerContent + * @param string $itemContent + * @param array $row + */ + public function preProcess(CorePageLayoutView &$parentObject, &$drawItem, &$headerContent, &$itemContent, array &$row) { + // Country detail + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_country_detail') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $countryUid = $flexFormData['settings']['countryUid']; + $name = $this->getName('country', $countryUid); + $itemContent = 'Detailseite Land
' . $name; + } + + // Region teaser + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_region_teasergroup') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $countryUid = $flexFormData['settings']['countryUid']; + $name = $this->getName('country', $countryUid); + $itemContent = 'Teasergruppe Gebiete
' . $name; + } + + // Region detail + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_region_detail') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $regionUid = $flexFormData['settings']['regionUid']; + $name = $this->getName('region', $regionUid); + $itemContent = 'Detailseite Gebiet
' . $name; + } + + // City teaser + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_city_teasergroup') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $countryUid = $flexFormData['settings']['countryUid']; + $regionUid = $flexFormData['settings']['regionUid']; + $record = []; + if ((int) $regionUid > 0) { + $name = $this->getName('region', $regionUid); + } elseif ((int) $countryUid > 0) { + $name = $this->getName('country', $countryUid); + } + $itemContent = 'Teasergruppe Orte
'; + if (count($record) > 0) { + $itemContent .= $name; + } + } + + // City detail + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_city_detail') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $cityUid = $flexFormData['settings']['cityUid']; + $name = $this->getName('city', $cityUid); + $itemContent = 'Detailseite Ort
' . $name; + } + + // Hotel Teaser + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_hotel_teasergroup') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $countryUid = $flexFormData['settings']['countryUid']; + $regionUid = $flexFormData['settings']['reguionUid']; + $cityUid = $flexFormData['settings']['cityUid']; + $name = ''; + if ((int) $cityUid > 0) { + $name = $this->getName('city', $cityUid); + } elseif ((int) $regionUid > 0) { + $name = $this->getName('region', $regionUid); + } elseif ((int) $countryUid > 0) { + $name = $this->getName('country', $countryUid); + } + $itemContent = 'Teasergruppe Hotels
' . $name; + } + + // Hotel detail + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_hotel_detail') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $hotelUid = $flexFormData['settings']['hotelUid']; + $name = $this->getName('hotel', $hotelUid); + $itemContent = 'Detailseite Hotel
' . $name; + } + + // FAQ + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_faq') { + $flexFormData = $this->getFlexFormData($row); + $faqUids = GeneralUtility::trimExplode(',', $flexFormData['settings']['faqUids']); + $drawItem = false; + $itemContent = 'FAQ
' . count($faqUids) . ' Frage(n)'; + } + + // Product pricetable + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_product_pricetable') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $itemContent = 'Buchungstabelle
'; + $productUid = $flexFormData['settings']['productUid']; + $record = BackendUtility::getRecord('tx_epproducts_domain_model_product', $productUid); + $itemContent .= $record['name']; + $hotelUid = $flexFormData['settings']['hotelUid']; + $record = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $hotelUid); + $itemContent .= ', ' . $record['name']; + } + + // Product detail + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_product_detail') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $itemContent = 'Produkt
'; + $productUid = $flexFormData['settings']['productUid']; + if ((int) $productUid === 0) { + $itemContent .= 'allgemein'; + } else { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_product', $productUid); + $itemContent .= $record['name_internal'] ? $record['name_internal'] : $record['name']; + $hotelUid = $flexFormData['settings']['hotelUid']; + if ((int) $hotelUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $hotelUid); + $itemContent .= ', ' . $record['name']; + } + } + } + + // Product teaser + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_product_teasergroup') { + $flexFormData = $this->getFlexFormData($row); + $mode = $this->getMode($flexFormData); + $drawItem = false; + switch ($mode) + { + case 'teasergroup': + $itemContent = 'Teasergruppe Produkte
'; + $filterParts = []; + $countryUid = $flexFormData['settings']['countryUid']; + if ((int) $countryUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_country', $countryUid); + $filterParts[] = $record['name']; + } + $regionUid = $flexFormData['settings']['regionUid']; + if ((int) $regionUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_region', $regionUid); + $filterParts[] = $record['name']; + } + $conceptUid = $flexFormData['settings']['conceptUid']; + if ((int) $conceptUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_concept', $conceptUid); + $filterParts[] = $record['name']; + } + $hotelUid = $flexFormData['settings']['hotelUid']; + if ((int) $hotelUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $hotelUid); + $filterParts[] = $record['name']; + } + if ($flexFormData['settings']['dateFrom']) { + $filterParts[] = 'von ' . date('d.m.Y', $flexFormData['settings']['dateFrom']); + } + if ($flexFormData['settings']['dateTo']) { + $filterParts[] = 'bis ' . date('d.m.Y', $flexFormData['settings']['dateTo']); + } + if ((bool) $flexFormData['settings']['busAvailable'] === true) { + $filterParts[] = 'Busreisen'; + } + $itemContent .= implode(', ', $filterParts); + break; + case 'single': + $itemContent = 'Teaser Produkt
'; + $productUid = $flexFormData['settings']['productUid']; + $record = BackendUtility::getRecord('tx_epproducts_domain_model_product', $productUid); + $itemContent .= $record['name']; + $hotelUid = $flexFormData['settings']['hotelUid']; + if ((int) $hotelUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $hotelUid); + $itemContent .= ', ' . $record['name']; + } + break; + } + } + + } + + /** + * @param array $row + * @return array + */ + protected function getFlexFormData(array $row) + { + /** @var FlexFormService $flexFormService */ + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + return $flexFormService->convertFlexFormContentToArray($row['pi_flexform']); + } + + /** + * @param array $flexFormData + * @return string + */ + protected function getMode(array $flexFormData) + { + $controllerAction = $flexFormData['switchableControllerActions']; + return substr($controllerAction, strpos($controllerAction, '>') + 1); + } + + /** + * @param string $type + * @param integer $uid + * + * @return string + */ + protected function getName($type, $uid) + { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_' . $type, $uid); + return $record['name']; + } + +} diff --git a/packages/ep_products/Classes/Hooks/Tcemain.php b/packages/ep_products/Classes/Hooks/Tcemain.php new file mode 100644 index 0000000..55ef7e7 --- /dev/null +++ b/packages/ep_products/Classes/Hooks/Tcemain.php @@ -0,0 +1,119 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Traits\DbConnectionTrait; +use TYPO3\CMS\Core\DataHandling\DataHandler; +use TYPO3\CMS\Core\Messaging\FlashMessage; +use TYPO3\CMS\Core\Messaging\FlashMessageService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; + +class Tcemain +{ + use DbConnectionTrait; + + /** + * @var array + */ + protected $tables = [ + 'tx_epproducts_domain_model_country', + 'tx_epproducts_domain_model_region', + 'tx_epproducts_domain_model_city' + ]; + + /** + * @param array $fields + * @param string $table + * @param int $id + * @param DataHandler $obj + * @throws \Doctrine\DBAL\DBALException + */ + public function processDatamap_preProcessFieldArray(array &$fields, $table, $id, DataHandler $obj) + { + // Render flash message in case of duplicate entry + if ($table === 'tx_epproducts_domain_model_matchcode' && $fields['code'] !== '') { + $code = $fields['code']; + $qb = $this->getDbConnection()->createQueryBuilder(); + $query = $qb + ->select('uid') + ->from('tx_epproducts_domain_model_matchcode') + ->where('code = :code') + ->andWhere('deleted = 0') + ->setParameter('code', $code) + ; + if (is_numeric($id)) { + $query + ->andWhere('uid != :uid') + ->setParameter('uid', $id) + ; + } + $existing = $query->execute()->fetch(); + if ($existing) { + $this->addFlashMessage('Der Hotelcode ' . $code . ' ist schon vergeben worden.', 'Hinweis', FlashMessage::WARNING); + } + } + if ($table === 'tx_epproducts_domain_model_product' && $fields['code'] !== '') { + $code = $fields['code']; + $qb = $this->getDbConnection()->createQueryBuilder(); + $query = $qb + ->select('uid') + ->from('tx_epproducts_domain_model_product') + ->where('code = :code') + ->andWhere('deleted = 0') + ->setParameter('code', $code); + if (is_numeric($id)) { + $query + ->andWhere('uid != :uid') + ->setParameter('uid', $id) + ; + } + $existing = $query->execute()->fetch(); + if ($existing) { + $this->addFlashMessage('Der Productcode ' . $code . ' ist schon vergeben worden.', 'Hinweis', FlashMessage::WARNING); + } + } + } + + /** + * @param string $message + * @param string $title + * @param int $severity + */ + protected function addFlashMessage($message, $title, $severity) + { + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */ + $flashMessageService = $objectManager->get(FlashMessageService::class); + $messageQueue = $flashMessageService->getMessageQueueByIdentifier(); + /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $message */ + $message = GeneralUtility::makeInstance(FlashMessage::class, $message, $title, $severity, true); + $messageQueue->addMessage($message); + } + +} diff --git a/packages/ep_products/Classes/Http/ApiKeyAuthenticator.php b/packages/ep_products/Classes/Http/ApiKeyAuthenticator.php new file mode 100644 index 0000000..3d87c03 --- /dev/null +++ b/packages/ep_products/Classes/Http/ApiKeyAuthenticator.php @@ -0,0 +1,45 @@ +extensionConfiguration = $extensionConfiguration; + } + + public function isAuthorized(ServerRequestInterface $request): bool + { + $configured = (string) ($this->extensionConfiguration->get('ep_products')['cmsApiKey'] ?? ''); + + if ('' === $configured) { + return false; + } + + // Header first: query-string keys end up in access logs. `key` stays accepted for parity + // with the existing /api/hotel and /api/product calls. + $provided = $request->getHeaderLine('X-Api-Key'); + + if ('' === $provided) { + $provided = (string) ($request->getQueryParams()['key'] ?? ''); + } + + return hash_equals($configured, $provided); + } +} diff --git a/packages/ep_products/Classes/Log/Writer.php b/packages/ep_products/Classes/Log/Writer.php new file mode 100644 index 0000000..f57728b --- /dev/null +++ b/packages/ep_products/Classes/Log/Writer.php @@ -0,0 +1,40 @@ +getData(); + if (!empty($recordData)) { + if (isset($recordData['exception']) && $recordData['exception'] instanceof \Exception) { + $recordData['exception'] = (string)$recordData['exception']; + } + $data = '- ' . json_encode($recordData); + } + + $fieldValues = [ + 'request_id' => $record->getRequestId(), + 'time_micro' => $record->getCreated(), + 'tstamp' => (int) $record->getCreated(), + 'component' => $record->getComponent(), + 'level' => LogLevel::normalizeLevel($record->getLevel()), + 'message' => $record->getMessage(), + 'data' => $data + ]; + + GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionForTable($this->logTable) + ->insert($this->logTable, $fieldValues); + + return $this; + } +} diff --git a/packages/ep_products/Classes/Middleware/ApiMiddleware.php b/packages/ep_products/Classes/Middleware/ApiMiddleware.php new file mode 100644 index 0000000..3edf691 --- /dev/null +++ b/packages/ep_products/Classes/Middleware/ApiMiddleware.php @@ -0,0 +1,328 @@ +hotelImageService = $hotelImageService; + $this->productImageService = $productImageService; + $this->regionImageService = $regionImageService; + $this->fileRepository = $fileRepository; + $this->imageService = $imageService; + $this->contentFeedApi = $contentFeedApi; + $this->authenticator = $authenticator; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $path = $request->getUri()->getPath(); + + if ('/api/hotel' === $path) { + return $this->handleHotelRequest($request); + } + + if ('/api/product' === $path) { + return $this->handleProductRequest($request); + } + + if (0 === strpos($path, ContentFeedApi::PATH_PREFIX)) { + return $this->contentFeedApi->handle($request); + } + + return $handler->handle($request); + } + + private function handleHotelRequest(ServerRequestInterface $request): ResponseInterface + { + if (!$this->authenticator->isAuthorized($request)) { + return new JsonResponse(['message' => 'Invalid or missing api key'], 403); + } + + $hotelCode = $request->getQueryParams()['hotel'] ?? null; + + if (null === $hotelCode) { + return new JsonResponse(['message' => 'No hotel code provided'], 400); + } + + $data = $this->fetchHotelData($hotelCode); + + if ([] === $data) { + return new JsonResponse(['message' => 'Hotel not found'], 404); + } + + return new JsonResponse($data); + } + + private function handleProductRequest(ServerRequestInterface $request): ResponseInterface + { + if (!$this->authenticator->isAuthorized($request)) { + return new JsonResponse(['message' => 'Invalid or missing api key'], 403); + } + + $productCode = $request->getQueryParams()['product'] ?? null; + $hotelCode = $request->getQueryParams()['hotel'] ?? null; + + if (null === $productCode) { + return new JsonResponse(['message' => 'No product code provided'], 400); + } + + $data = $this->fetchProductData($productCode); + + if (null === $data) { + return new JsonResponse(['message' => 'Product not found'], 404); + } + + if (null !== $hotelCode) { + $data['hotel'] = $this->fetchHotelData($hotelCode); + } + + return new JsonResponse($data); + } + + private function fetchProductData(string $productCode): ?array + { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_epproducts_domain_model_product'); + + $data = $queryBuilder + ->select( + 'product.uid as productUid', + 'product.name as productName', + 'product.code as productCode', + 'product.region as regionUid', + 'country.name as countryName', + 'city.name as cityName', + ) + ->from('tx_epproducts_domain_model_product', 'product') + ->leftJoin('product', 'tx_epproducts_domain_model_country', 'country', 'product.country = country.uid') + ->leftJoin('product', 'tx_epproducts_domain_model_city', 'city', 'product.city = city.uid') + ->leftJoin('product', 'tx_epproducts_domain_model_matchcode', 'matchcode', 'product.uid = matchcode.entity AND matchcode.entity_type="hotel"') + ->where($queryBuilder->expr()->eq('product.code', $queryBuilder->createNamedParameter($productCode))) + ->orWhere($queryBuilder->expr()->eq('matchcode.code', $queryBuilder->createNamedParameter($productCode))) + ->setMaxResults(1) + ->execute() + ->fetchAssociative() + ; + + if (false === $data) { + return null; + } + + $images = $this->serializeImages($this->productImageService->getImages((int) $data['productUid'])); + + return [ + 'product' => [ + 'name' => $data['productName'], + 'code' => $data['productCode'], + 'images' => $images, + ], + 'country' => [ + 'name' => $data['countryName'], + ], + 'region' => $this->fetchRegionData((int) $data['regionUid']), + 'city' => [ + 'name' => $data['cityName'], + ], + ]; + } + + private function fetchHotelData(string $hotelCode): array + { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_epproducts_domain_model_hotel'); + + $hotel = $queryBuilder + ->select( + 'hotel.uid', + 'hotel.name', + 'hotel.code', + 'hotel.description', + 'hotel.features', + 'hotel.room_types', + 'hotel.additional_information', + 'hotel.address', + 'hotel.beds_count', + 'hotel.parking_count', + 'hotel.altitude', + 'hotel.shower', + 'hotel.partyroom', + 'hotel.gamesroom', + 'hotel.sauna', + 'hotel.wifi', + 'hotel.distance_bus', + 'hotel.distance_lift', + 'hotel.region', + ) + ->from('tx_epproducts_domain_model_hotel', 'hotel') + ->leftJoin('hotel', 'tx_epproducts_domain_model_matchcode', 'alias', 'hotel.uid = alias.entity AND alias.entity_type="hotel"') + ->where( + $queryBuilder->expr()->orX( + $queryBuilder->expr()->eq('hotel.code', $queryBuilder->createNamedParameter($hotelCode)), + $queryBuilder->expr()->eq('alias.code', $queryBuilder->createNamedParameter($hotelCode)), + ) + ) + ->setMaxResults(1) + ->execute() + ->fetchAssociative() + ; + + if (false === $hotel) { + return []; + } + + $images = $this->serializeImages($this->hotelImageService->getImages((int) $hotel['uid'])); + + return [ + 'name' => $hotel['name'], + 'code' => $hotel['code'], + 'description' => $hotel['description'], + 'features' => $hotel['features'], + 'room_types' => $hotel['room_types'], + 'additional_information' => $hotel['additional_information'], + 'address' => $hotel['address'], + 'images' => $images, + 'icons' => HotelIconUtility::getIcons($hotel), + 'region' => $this->fetchRegionData((int) $hotel['region']), + ]; + } + + private function fetchRegionData(int $regionUid): array + { + if (0 === $regionUid) { + return []; + } + + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_epproducts_domain_model_region'); + + $region = $queryBuilder + ->select( + 'region.uid', + 'region.name', + 'region.latitude', + 'region.longitude', + 'region.ski_area', + 'region.ski_area_extended', + 'region.description', + 'region.news', + 'region.length', + 'region.height', + 'region.lifts', + 'webcam.url as webcamUrl', + ) + ->from('tx_epproducts_domain_model_region', 'region') + ->leftJoin('region', 'tx_epproducts_domain_model_webcam', 'webcam', 'region.webcam = webcam.uid') + ->where($queryBuilder->expr()->eq('region.uid', $queryBuilder->createNamedParameter($regionUid, \PDO::PARAM_INT))) + ->setMaxResults(1) + ->execute() + ->fetchAssociative() + ; + + if (false === $region) { + return []; + } + + $images = $this->serializeImages($this->regionImageService->getImages((int) $region['uid'])); + $regionMaps = $this->serializeFileReferences( + $this->fileRepository->findByRelation('tx_epproducts_domain_model_region', 'region_maps', (int) $region['uid']) + ); + + return [ + 'name' => $region['name'], + 'latitude' => $region['latitude'], + 'longitude' => $region['longitude'], + 'webcam' => $region['webcamUrl'], + 'ski_area' => $region['ski_area'], + 'ski_area_extended' => $region['ski_area_extended'], + 'description' => $region['description'], + 'news' => $region['news'], + 'length' => $region['length'], + 'altitude' => $region['height'], + 'lifts' => $region['lifts'], + 'images' => $images, + 'region_maps' => $regionMaps, + ]; + } + + /** + * The 'original' entry of AbstractImageService::getImages() holds raw FileReference objects, + * which are needed by Fluid templates elsewhere but serialize to empty objects in JSON responses. + * We replace it with the same {url, alt, title} shape used for 'region_maps'. + */ + private function serializeImages(array $images): array + { + return [ + 'original' => $this->serializeFileReferences($images['original']), + 'resized' => $images['resized'], + ]; + } + + private function serializeFileReferences(array $files): array + { + return array_map(function ($file) { + return [ + 'url' => $this->imageService->getImageUri($file, true), + 'alt' => $file->getProperty('alternative'), + 'title' => $file->getProperty('title'), + ]; + }, $files); + } +} diff --git a/packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php b/packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php new file mode 100644 index 0000000..4f3409a --- /dev/null +++ b/packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php @@ -0,0 +1,65 @@ +authenticator = $authenticator; + $this->configuration = $configuration; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $routing = $request->getAttribute('routing'); + + if (!$routing instanceof PageArguments + || (string) ManifestBuilder::CONTENT_PAGE_TYPE !== $routing->getPageType() + ) { + return $handler->handle($request); + } + + if (!$this->authenticator->isAuthorized($request)) { + // 404, not 403: an unauthenticated caller should not learn the type exists. + return GeneralUtility::makeInstance(ErrorController::class) + ->pageNotFoundAction($request, 'The requested page does not exist'); + } + + if (!$this->configuration->isEnabled()) { + return ContentFeedApi::disabledResponse(); + } + + return $handler->handle($request); + } +} diff --git a/packages/ep_products/Classes/Middleware/StagingEnvironmentMiddleware.php b/packages/ep_products/Classes/Middleware/StagingEnvironmentMiddleware.php new file mode 100644 index 0000000..f71aec7 --- /dev/null +++ b/packages/ep_products/Classes/Middleware/StagingEnvironmentMiddleware.php @@ -0,0 +1,43 @@ +getQueryParams(); + + if (!isset($queryParams['ep_staging'])) { + return $handler->handle($request); + } + + $isStaging = '1' === (string) $queryParams['ep_staging']; + + // Update $_COOKIE so the value is available during the current request + if ($isStaging) { + $_COOKIE[self::COOKIE_NAME] = '1'; + } else { + unset($_COOKIE[self::COOKIE_NAME]); + } + + $response = $handler->handle($request); + + // Set the cookie on the response for subsequent browser requests + if ($isStaging) { + $cookie = new Cookie(self::COOKIE_NAME, '1', 0, '/', null, false, true, false, Cookie::SAMESITE_LAX); + } else { + $cookie = new Cookie(self::COOKIE_NAME, '', 1, '/', null, false, true, false, Cookie::SAMESITE_LAX); + } + + return $response->withAddedHeader('Set-Cookie', (string) $cookie); + } +} diff --git a/packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php b/packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php new file mode 100644 index 0000000..82fbeb8 --- /dev/null +++ b/packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php @@ -0,0 +1,71 @@ +getAttribute('normalizedParams'); + $uri = $params->getRequestUri(); + + if (strpos($uri, self::PREFIX_PATH_SEGMENT) === false) { + return $handler->handle($request); + } + + // Ensure trailing slash for comparison + $uri = rtrim($uri, '/') . '/'; + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $query = $qb + ->select('*') + ->from('tx_epproducts_domain_model_staticsearchparams') + ->where('CONCAT("/", :prefix, "/", path_segment, "/")=LOWER(:uri)') + ->andWhere('deleted = 0') + ->andWhere('hidden = 0') + ->setParameters([ + 'prefix' => self::PREFIX_PATH_SEGMENT, + 'uri' => $uri, + ]) + ; + + $row = $query->execute()->fetchAssociative(); + + if (!$row) { + return $handler->handle($request); + } + + $filterService = GeneralUtility::makeInstance(FilterService::class); + + $filterSettings = $filterService->getFilterSettingsFromStaticSearchParams($row); + $filterSettingsEncoder = new FilterSettingsEncoder(); + $encodedFilterSettings = $filterSettingsEncoder->encode($filterSettings); + + $searchPageUid = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products', 'searchPageUid'); + + $site = $request->getAttribute('site'); + $redirectUri = $site->getRouter()->generateUri($searchPageUid, ['f' => $encodedFilterSettings]); + + return new RedirectResponse($redirectUri, self::REDIRECT_RESPONSE_CODE); + } +} diff --git a/packages/ep_products/Classes/MyEP/ApiClient.php b/packages/ep_products/Classes/MyEP/ApiClient.php new file mode 100644 index 0000000..ca90c3b --- /dev/null +++ b/packages/ep_products/Classes/MyEP/ApiClient.php @@ -0,0 +1,506 @@ +requestJson('GET', 'last-update'); + + if (!isset($data['timestamp']) || null === $data['timestamp'] || '' === $data['timestamp']) { + return null; + } + + try { + // The API reports Europe/Berlin local time without an offset. + return new \DateTimeImmutable($data['timestamp'], new \DateTimeZone('Europe/Berlin')); + } catch (\Throwable $e) { + $message = sprintf('Invalid timestamp returned by MyEP API for GET last-update: %s', $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } + } + + /** + * One page of the product export: one row per travel date × hotel in `data`, + * and `meta` describing the whole export. + * + * @throws ApiException + */ + public function getProductExport(int $page, int $perPage): array + { + return $this->requestJson('GET', 'exports/products', [ + 'query' => [ + 'page' => $page, + 'perPage' => $perPage, + ], + ]); + } + + /** + * @throws ApiException + */ + public function getPickups(): array + { + return $this->requestJson('GET', 'pickups'); + } + + /** + * @throws ApiException + */ + public function getTravels(): array + { + return $this->requestJson('GET', 'travels'); + } + + /** + * @throws ApiException + */ + public function registerAddress(array $addressData): void + { + $response = $this->request('POST', 'contactform', [ + 'json' => $addressData, + ]); + + // A conflict means the address is already registered, which is the normal answer to a + // resubmitted contact form and not worth reporting. + $this->assertStatusCode($response, [Response::HTTP_OK, Response::HTTP_CREATED, Response::HTTP_ACCEPTED, Response::HTTP_NO_CONTENT, Response::HTTP_CONFLICT], 'POST', 'contactform'); + $this->drainResponse($response); + } + + /** + * @throws ApiException + */ + public function getNewsletterLists(): array + { + return $this->requestJson('GET', 'newsletters'); + } + + /** + * @throws ApiException + */ + public function subscribeToNewsletters(NewsletterSubscriptionRequest $subscriptionRequest): array + { + $response = $this->request('POST', 'newsletter-subscriptions', [ + 'json' => $subscriptionRequest, + ]); + + $this->assertStatusCode($response, [Response::HTTP_OK, Response::HTTP_ACCEPTED], 'POST', 'newsletter-subscriptions'); + + return $this->decodeResponse($response, 'POST', 'newsletter-subscriptions'); + } + + /** + * @throws ApiException + */ + public function getTravel(string $productCode): array + { + return $this->requestJson('GET', 'travels/' . $productCode); + } + + /** + * @throws ApiException + */ + public function getPickupsPlanning(string $travelCode): array + { + return $this->requestJson('GET', 'pickups-planning/' . $travelCode); + } + + /** + * @throws ApiException + */ + public function getPriceConfigData(string $hotelCode): array + { + return $this->requestJson('GET', 'contingents/calendar', [ + 'query' => [ + 'hotelCode' => $hotelCode, + ], + ]); + } + + /** + * @throws ApiException + */ + public function getPriceTableData(string $hotelCode, string $year): array + { + return $this->requestJson('GET', 'contingents/prices', [ + 'query' => [ + 'hotelCode' => $hotelCode, + 'year' => $year, + ], + ]); + } + + /** + * @throws ApiException + */ + private function requestJson(string $method, string $uri, array $options = [], array $expectedStatusCodes = [Response::HTTP_OK]): array + { + $response = $this->request($method, $uri, $options); + + $this->assertStatusCode($response, $expectedStatusCodes, $method, $uri); + + return $this->decodeResponse($response, $method, $uri); + } + + /** + * @throws ApiException + */ + private function request(string $method, string $uri, array $options = []): ResponseInterface + { + $response = $this->sendRequest($method, $uri, $options); + + if (false === in_array($this->readStatusCode($response, $method, $uri), self::UNAUTHORIZED_STATUS_CODES, true)) { + return $response; + } + + // The token outlives a single PHP request, so one the API has stopped accepting - after + // a MyEP restart or a revoked client - would keep failing every call for the rest of its + // cached lifetime unless it is actively discarded here. + $this->logWarning(sprintf('MyEP API rejected the cached access token for %s %s, re-authenticating once', $method, $uri)); + $this->discardAccessToken(); + + return $this->sendRequest($method, $uri, $options); + } + + /** + * @throws ApiException + */ + private function sendRequest(string $method, string $uri, array $options): ResponseInterface + { + // Resolved here rather than baked into the client's default options so that the shared + // client survives a token change, and so the retry above picks up the fresh token. + $options['auth_bearer'] = $this->getAccessToken($this->getConfiguration())->getToken(); + + try { + return $this->getHttpClient()->request($method, $uri, $options); + } catch (TransportExceptionInterface $e) { + $message = sprintf('MyEP API transport error for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } + } + + /** + * @param int[] $expectedStatusCodes + * + * @throws ApiException + */ + private function assertStatusCode(ResponseInterface $response, array $expectedStatusCodes, string $method, string $uri): void + { + $statusCode = $this->readStatusCode($response, $method, $uri); + + if (true === in_array($statusCode, $expectedStatusCodes, true)) { + return; + } + + $message = sprintf('MyEP API returned unexpected status %d for %s %s', $statusCode, $method, $uri); + $this->logWarning($message); + + throw new ApiException($message, $statusCode); + } + + /** + * Reading the status completes the transfer, so this is also what turns the lazy response + * returned by the client into a finished request. Symfony keeps the status on the response, + * so calling this twice costs nothing. + * + * @throws ApiException + */ + private function readStatusCode(ResponseInterface $response, string $method, string $uri): int + { + try { + return $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + $message = sprintf('MyEP API transport error while reading status for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } + } + + /** + * Reads and discards a body nobody else is going to look at. Destroying an unconsumed + * response cancels the transfer, which closes the connection instead of returning it to + * the pool, so the next call would have to handshake again. + */ + private function drainResponse(ResponseInterface $response): void + { + try { + $response->getContent(false); + } catch (TransportExceptionInterface $e) { + // The status has already been asserted, so a failure here costs nothing but the + // connection reuse this method exists for. + } + } + + /** + * @throws ApiException + */ + private function decodeResponse(ResponseInterface $response, string $method, string $uri): array + { + try { + return $response->toArray(false); + } catch (DecodingExceptionInterface $e) { + $message = sprintf('MyEP API returned invalid JSON for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } catch (TransportExceptionInterface $e) { + $message = sprintf('MyEP API transport error while reading body for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } + } + + private function getHttpClient(): HttpClientInterface + { + if (null !== static::$httpClient) { + return static::$httpClient; + } + + $config = $this->getConfiguration(); + + $options = []; + + // Without these the client falls back to PHP's default_socket_timeout (commonly 60s), + // so a stalled API pins a PHP worker for a minute per request. The defaults live in + // code rather than in ext_conf_template.txt: ExtensionConfiguration::get() returns the + // stored configuration verbatim and only syncs the template when an extension has no + // configuration at all, so a newly added template key is absent until someone saves + // extension configuration by hand. Falling back to 0 there would silently mean "no + // timeout" - the opposite of what this guard is for. An explicit 0 still disables. + $timeout = (int)($config['myEpApiTimeout'] ?? self::DEFAULT_TIMEOUT); + if ($timeout > 0) { + $options['timeout'] = $timeout; + } + $maxDuration = (int)($config['myEpApiMaxDuration'] ?? self::DEFAULT_MAX_DURATION); + if ($maxDuration > 0) { + $options['max_duration'] = $maxDuration; + } + + return static::$httpClient = HttpClient::createForBaseUri($config['myEpApiBaseUrl'], $options); + } + + /** + * Resolved lazily rather than injected: this class is also instantiated through Extbase's + * legacy object container (EP\EpTheme\Form\BpnApiFinisher asks for it via injectApiClient), + * and that container resolves constructor arguments by reflection, mapping an interface to + * a class name by stripping the "Interface" suffix (Container::getImplementationClassName()). + * A constructor typed against FrontendInterface therefore fatals there with + * "Class TYPO3\CMS\Core\Cache\Frontend\Frontend does not exist". + */ + private function getCache(): FrontendInterface + { + if (null === $this->cache) { + $this->cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('ep_products_cache'); + } + + return $this->cache; + } + + private function getConfiguration(): array + { + if (null === $this->configuration) { + $this->configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); + } + + return $this->configuration; + } + + /** + * @throws ApiException + */ + private function getAccessToken(array $config): AccessToken + { + if (true === $this->isTokenUsable(static::$accessToken)) { + return static::$accessToken; + } + + // The static above only lives for one PHP request, so without this second level every + // single request would re-authenticate before it could fetch any data. + $cachedToken = $this->getCache()->get(self::ACCESS_TOKEN_CACHE_IDENTIFIER); + if (is_array($cachedToken) && isset($cachedToken['access_token'])) { + $token = new AccessToken($cachedToken); + if (true === $this->isTokenUsable($token)) { + static::$accessToken = $token; + + return static::$accessToken; + } + } + + try { + static::$accessToken = $this->normalizeToken($this->getProvider($config)->getAccessToken('client_credentials')); + $this->cacheAccessToken(static::$accessToken); + } catch (IdentityProviderException $e) { + $message = sprintf('MyEP OAuth token request failed: %s', $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } catch (\UnexpectedValueException $e) { + $message = sprintf('MyEP OAuth token response was invalid: %s', $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } catch (GuzzleException $e) { + $message = sprintf('MyEP OAuth token transport failed: %s', $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } + + return static::$accessToken; + } + + /** + * AccessToken::hasExpired() throws for a token that reports no expiry, so it cannot be used + * as the check - a token without one is simply treated as unusable and replaced. The same + * margin as the cache lifetime is applied on read as well, so a token cannot lapse between + * this check and the API call that uses it, whichever node wrote the cache entry. + */ + private function isTokenUsable(?AccessToken $token): bool + { + if (null === $token) { + return false; + } + + $expires = $token->getExpires(); + + if (null === $expires) { + return false; + } + + return $expires - self::ACCESS_TOKEN_EXPIRY_MARGIN > time(); + } + + /** + * A token that reports no expiry cannot be validated on read - isTokenUsable() rejects it + * and AccessToken::hasExpired() throws for it - so it is given the fallback lifetime here, + * once, and the rest of the class can treat every token the same way. + */ + private function normalizeToken(AccessToken $token): AccessToken + { + if (null !== $token->getExpires()) { + return $token; + } + + return new AccessToken($token->jsonSerialize() + ['expires_in' => self::ACCESS_TOKEN_DEFAULT_TTL]); + } + + private function discardAccessToken(): void + { + static::$accessToken = null; + $this->getCache()->remove(self::ACCESS_TOKEN_CACHE_IDENTIFIER); + } + + private function cacheAccessToken(AccessToken $token): void + { + // normalizeToken() has already given a token without a reported expiry the fallback + // lifetime, so there is always a real expiry to subtract the margin from here. + $lifetime = $token->getExpires() - time() - self::ACCESS_TOKEN_EXPIRY_MARGIN; + + if ($lifetime <= 0) { + return; + } + + $this->getCache()->set(self::ACCESS_TOKEN_CACHE_IDENTIFIER, $token->jsonSerialize(), [], $lifetime); + } + + private function getProvider(array $config): AbstractProvider + { + return new GenericProvider([ + // The OAuth round trip goes through the provider's own Guzzle client, not the + // Symfony client configured in getHttpClient(), so it needs its own timeout - + // otherwise the token request hangs indefinitely and the data-request timeout + // never gets a chance to apply. AbstractProvider::getAllowedClientOptions() + // forwards 'timeout' (and 'proxy') into Guzzle; 0 means unlimited there too. + 'timeout' => (int)($config['myEpApiTimeout'] ?? self::DEFAULT_TIMEOUT), + 'clientId' => $config['myEpApiClientId'], + 'clientSecret' => $config['myEpApiClientSecret'], + 'redirectUri' => null, + 'urlAuthorize' => $config['myEpApiUrlAuthorize'], + 'urlAccessToken' => $config['myEpApiUrlToken'], + 'urlResourceOwnerDetails' => null, + 'scopes' => 'api', + ]); + } + + private function logError(string $message, array $context = []): void + { + if (null !== $this->logger) { + $this->logger->error($message, $context); + } + } + + private function logWarning(string $message, array $context = []): void + { + if (null !== $this->logger) { + $this->logger->warning($message, $context); + } + } +} diff --git a/packages/ep_products/Classes/MyEP/ApiException.php b/packages/ep_products/Classes/MyEP/ApiException.php new file mode 100644 index 0000000..b59a70c --- /dev/null +++ b/packages/ep_products/Classes/MyEP/ApiException.php @@ -0,0 +1,7 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Routing\Aspect\StaticMappableAspectInterface; + +class PassthroughMapper implements StaticMappableAspectInterface +{ + + /** + * @inheritDoc + */ + public function generate(string $value): ?string + { + return $value; + } + + /** + * @inheritDoc + */ + public function resolve(string $value): ?string + { + return $value; + } +} \ No newline at end of file diff --git a/packages/ep_products/Classes/Service/AbstractImageService.php b/packages/ep_products/Classes/Service/AbstractImageService.php new file mode 100644 index 0000000..070d038 --- /dev/null +++ b/packages/ep_products/Classes/Service/AbstractImageService.php @@ -0,0 +1,111 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Imaging\ImageManipulation\Area; +use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection; +use TYPO3\CMS\Core\Resource\FileReference; +use TYPO3\CMS\Core\Resource\FileRepository; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Extbase\Service\ImageService; + +abstract class AbstractImageService implements SingletonInterface +{ + /** + * @var FileRepository + */ + protected $fileRepository; + + /** + * @var ImageService + */ + protected $imageService; + + /** + * @var FrontendInterface + */ + protected $cache; + + public function __construct(FileRepository $fileRepository, ImageService $imageService, FrontendInterface $cache) + { + $this->fileRepository = $fileRepository; + $this->imageService = $imageService; + $this->cache = $cache; + } + + public function getImages($uid): array + { + $key = sprintf('images-%s-%d', $this->table, $uid); + if (false === ($value = $this->cache->get($key))) { + $original = $this->fileRepository->findByRelation( + $this->table, + $this->field, + $uid + ); + $resized = $this->generateResizedImages($original); + $value = [ + 'original' => $original, + 'resized' => $resized, + ]; + $this->cache->set($key, $value); + } + + return $value; + } + + public function generateResizedImages($images): array + { + $resized = []; + foreach ($this->imageSizes as $version => $instructions) + { + foreach ($images as $image) + { + $instructions['crop'] = $this->getCroppingInfo($image, $instructions); + $processedImage = $this->imageService->applyProcessingInstructions($image, $instructions); + $resized[$version][] = [ + 'url' => $this->imageService->getImageUri($processedImage, true), + 'alt' => $image->getProperty('alternative'), + 'title' => $image->getProperty('title'), + ]; + } + } + + return $resized; + } + + protected function getCroppingInfo(FileReference $image, array $processingInstructions): ?Area + { + $cropString = $image->getProperty('crop'); + $cropVariantCollection = CropVariantCollection::create((string) $cropString); + $cropVariant = $processingInstructions['cropVariant'] ?: 'default'; + $cropArea = $cropVariantCollection->getCropArea($cropVariant); + + return $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image); + } +} diff --git a/packages/ep_products/Classes/Service/ApiImportSource.php b/packages/ep_products/Classes/Service/ApiImportSource.php new file mode 100644 index 0000000..4db2f73 --- /dev/null +++ b/packages/ep_products/Classes/Service/ApiImportSource.php @@ -0,0 +1,233 @@ +apiClient = $apiClient; + } + + /** + * The whole export is fetched before anything is returned, so a walk that + * has to restart never leaves a stitched dataset behind. + * + * @throws ApiException + */ + public function acquire(): ?ProductImport + { + if (!$this->isNewImportAvailable()) { + return null; + } + + for ($walk = 1; $walk <= self::MAX_WALKS; $walk++) { + $export = $this->walkPages(); + if ($export === false) { + continue; + } + if ($export === null) { + return null; + } + + [$rows, $sourceGeneratedAt] = $export; + $baseline = $this->getImportedTimestamp(); + if ($baseline !== null && $sourceGeneratedAt <= $baseline) { + return null; + } + + return new ProductImport($rows, $sourceGeneratedAt); + } + + throw new ApiException(sprintf( + 'MyE&P replaced its product export during each of %d page walks, giving up until the next run', + self::MAX_WALKS + )); + } + + public function markImported(ProductImport $import): void + { + $sourceGeneratedAt = $import->getSourceGeneratedAt(); + if ($sourceGeneratedAt !== null) { + $file = $this->baselinePath(); + GeneralUtility::mkdir_deep(dirname($file)); + GeneralUtility::writeFile($file, $sourceGeneratedAt->format(\DateTimeInterface::ATOM), false); + } + + (new ImportTimestampService())->writeImportTimestamp(self::IMPORT_MARKER); + } + + /** + * True when MyE&P reports an export newer than the last imported one, or + * nothing has been imported yet. A missing timestamp from MyE&P means + * "not importable now", never "import". + * + * @throws ApiException + */ + public function isNewImportAvailable(): bool + { + $lastUpdateAt = $this->getApiClient()->getLastUpdateAt(); + if ($lastUpdateAt === null) { + return false; + } + + $baseline = $this->getImportedTimestamp(); + + return $baseline === null || $lastUpdateAt > $baseline; + } + + /** + * `meta.sourceGeneratedAt` of the last successfully imported export. + */ + public function getImportedTimestamp(): ?\DateTimeImmutable + { + $file = $this->baselinePath(); + if (!is_file($file)) { + return null; + } + + return $this->parseTimestamp(trim((string) file_get_contents($file))); + } + + /** + * @return array{0: array[], 1: \DateTimeImmutable}|false|null the rows and + * the export's timestamp; null when the export is not importable now; + * false when the export changed mid-walk and the walk must restart + * @throws ApiException + */ + private function walkPages() + { + $rows = []; + $sourceGeneratedAt = null; + $page = 1; + + do { + $body = $this->getApiClient()->getProductExport($page, self::PER_PAGE); + $meta = $body['meta'] ?? null; + if (!is_array($meta) || !isset($body['data']) || !is_array($body['data'])) { + throw new ApiException(sprintf('MyE&P product export page %d is malformed', $page)); + } + + $pageGeneratedAt = $this->parseTimestamp($meta['sourceGeneratedAt'] ?? null); + if ($page === 1) { + if ($pageGeneratedAt === null) { + $this->logWarning('MyE&P product export has no source timestamp; skipping product import'); + return null; + } + $sourceGeneratedAt = $pageGeneratedAt; + } elseif ($pageGeneratedAt === null || $pageGeneratedAt != $sourceGeneratedAt) { + $this->logWarning('MyE&P product export changed during the page walk; restarting', [ + 'page' => $page, + 'expected' => $sourceGeneratedAt->format(\DateTimeInterface::ATOM), + 'actual' => $meta['sourceGeneratedAt'] ?? null, + ]); + return false; + } + + foreach ($body['data'] as $row) { + $rows[] = $row; + } + + $totalPages = (int) ($meta['totalPages'] ?? 0); + $page++; + } while ($page <= $totalPages); + + $total = (int) ($meta['total'] ?? -1); + if (count($rows) !== $total) { + throw new ApiException(sprintf( + 'MyE&P product export is incomplete: %d rows received, %d announced', + count($rows), + $total + )); + } + + if ((int) ($meta['skipped'] ?? 0) > 0) { + $this->logWarning('MyE&P skipped travel dates with unparseable dates', ['skipped' => $meta['skipped']]); + } + if ((int) ($meta['pickupsWithoutMasterData'] ?? 0) > 0) { + $this->logWarning( + 'MyE&P product export has pickups without master data', + ['pickupsWithoutMasterData' => $meta['pickupsWithoutMasterData']] + ); + } + + return [$rows, $sourceGeneratedAt]; + } + + /** + * @param mixed $value + */ + private function parseTimestamp($value): ?\DateTimeImmutable + { + if (!is_string($value) || $value === '') { + return null; + } + + try { + return new \DateTimeImmutable($value); + } catch (\Exception $e) { + return null; + } + } + + private function getApiClient(): ApiClient + { + if ($this->apiClient === null) { + $this->apiClient = GeneralUtility::makeInstance(ApiClient::class); + } + + return $this->apiClient; + } + + private function baselinePath(): string + { + return Environment::getVarPath() . '/transient/' . self::STAGING_SUBDIR . '/' . self::BASELINE_FILE; + } + + private function logWarning(string $message, array $context = []): void + { + if ($this->logger !== null) { + $this->logger->warning($message, $context); + } + } +} diff --git a/packages/ep_products/Classes/Service/ContingentDataService.php b/packages/ep_products/Classes/Service/ContingentDataService.php new file mode 100644 index 0000000..2c78761 --- /dev/null +++ b/packages/ep_products/Classes/Service/ContingentDataService.php @@ -0,0 +1,113 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\ContingentRepository; +use EP\EpProducts\Utility\BookingUrlUtility; +use League\Period\Period; + +class ContingentDataService +{ + /** + * @var ContingentRepository + */ + protected $contingentRepository; + + /** + * @param ContingentRepository $contingentRepository + */ + public function __construct(ContingentRepository $contingentRepository) + { + $this->contingentRepository = $contingentRepository; + } + + /** + * @param Hotel $hotel + * @param Product $product + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getAvailableContingents(Hotel $hotel, Product $product) + { + $data = $this->contingentRepository->getAvailableContingents($hotel, $product); + $contingents = []; + foreach ($data as $row) { + $contingents[$row['date']] = [ + 'date' => $row['date'], + 'minNights' => $row['minNights'], + 'total' => $row['total'], + 'capacity' => $row['capacity'], + ]; + } + + return $contingents; + } + + /** + * @param string $dateFrom + * @param string $dateTo + * @param Hotel $hotel + * @param Product $product + * @param string $myEpUrl + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getAvailableRooms( + string $dateFrom, + string $dateTo, + Hotel $hotel, + Product $product, + string $myEpUrl + ) { + $data = $this->contingentRepository->getAvailableRooms($dateFrom, $dateTo, $hotel, $product); + $period = new Period($dateFrom, $dateTo); + $nights = $period->dateInterval()->format('%a'); + + $rooms = []; + foreach ($data as $row) { + $row['bookingUrl'] = BookingUrlUtility::generateUrl([ + 'dateId' => $row['busProId'], + 'hotelId' => $row['hotelBusProId'], + 'myEpUrl' => $myEpUrl, + ]); + + $row['summer'] = $row['season'] === 's'; + + $row['servicesIncluded'] = unserialize($row['servicesIncluded'], ['allowed_classes' => false]); + $row['servicesOptional'] = unserialize($row['servicesOptional'], ['allowed_classes' => false]); + + $row['priceForSelection'] = $row['price'] + ($nights - $row['minNights']) * $row['additionalNightPrice']; + + $rooms[] = $row; + } + + return $rooms; + } +} diff --git a/packages/ep_products/Classes/Service/ContingentImportService.php b/packages/ep_products/Classes/Service/ContingentImportService.php new file mode 100644 index 0000000..bf99af8 --- /dev/null +++ b/packages/ep_products/Classes/Service/ContingentImportService.php @@ -0,0 +1,341 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use Doctrine\DBAL\Connection; +use EP\EpProducts\Domain\Model\Contingent; +use EP\EpProducts\Traits\DbConnectionTrait; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; + +class ContingentImportService implements SingletonInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + use DbConnectionTrait; + + public const XML_PATH = 'fileadmin/xmlexportzimmer'; + public const ROOMCODE_STATUS = 'BelKal'; + + /** + * @var string[] + */ + // protected $ignoredRoomTypes = ['PDGS', 'TEAM']; + protected $ignoredRoomTypes = ['PDGS']; + + /** + * @var ConfigurationManagerInterface + */ + protected $configurationManager; + + /** + * @var int + */ + protected $contingentsPid = 0; + + /** + * @var int + */ + protected $hotelsPid = 0; + + /** + * @var Connection + */ + protected $db; + + /** + * @throws \Doctrine\DBAL\DBALException + */ + public function __construct(ConfigurationManagerInterface $manager) + { + $this->configurationManager = $manager; + + $settings = GeneralUtility::removeDotsFromTS( + $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + + if (isset($settings['plugin']['tx_epproducts']['persistence']['storagePid'])) { + $this->contingentsPid = $settings['plugin']['tx_epproducts']['persistence']['storagePid']; + } elseif (isset($settings['module']['tx_epproducts']['persistence']['storagePid'])) { + $this->contingentsPid = $settings['module']['tx_epproducts']['persistence']['storagePid']; + } + + if (isset($settings['plugin']['tx_epproducts']['settings']['hotelsPid'])) { + $this->hotelsPid = $settings['plugin']['tx_epproducts']['settings']['hotelsPid']; + } elseif (isset($settings['module']['tx_epproducts']['settings']['hotelsPid'])) { + $this->hotelsPid = $settings['module']['tx_epproducts']['settings']['hotelsPid']; + } + + $this->db = $this->getDbConnection(); + } + + /** + * @return int + * + * @throws \Doctrine\DBAL\DBALException + */ + public function import() + { + $this->createTempTable(); + $this->logger->info('starting product import'); + $contingentCount = $this->parseXmlFilesInFolder(); + $this->clearTable(); + $this->copyTable(); + $this->clearTempTable(); + + return $contingentCount; + } + + public function isImportRequired(): bool + { + $timestampService = new ImportTimestampService(); + + return $timestampService->isImportRequired( + 'fileadmin/xmlexportzimmer/uebertragung.info', + 'fileadmin/contingents_import.info' + ); + } + + /** + * @return int + */ + public function parseXmlFilesInFolder() + { + $dateCount = 0; + $path = GeneralUtility::getFileAbsFileName(self::XML_PATH); + $folder = opendir($path); + while (($fileName = readdir($folder)) !== false) { + if (0 === strpos($fileName, 'HotelZimmer')) { + $dateCount += $this->parseContingentXmlFile($path, $fileName); + } + } + + return $dateCount; + } + + /** + * @param string $fileName + * + * @return int + */ + public function parseContingentXmlFile($path, $fileName) + { + $filePath = sprintf('%s/%s', $path, $fileName); + + if (null === $xmlData = $this->loadXmlFile($filePath)) { + return 0; + } + + return $this->parseContingent($xmlData); + } + + public function loadXmlFile($file): ?\SimpleXMLElement + { + libxml_use_internal_errors(true); + if (!file_exists($file)) { + return null; + } + /** @var \SimpleXMLElement $xmlData */ + if (!$xmlData = simplexml_load_string(file_get_contents($file))) { + libxml_clear_errors(); + + return null; + } + + return $xmlData; + } + + /** + * @throws \Doctrine\DBAL\DBALException + */ + public function createTempTable() + { + $this->db->executeStatement('CREATE TABLE IF NOT EXISTS tx_epproducts_domain_model_contingent_temp LIKE tx_epproducts_domain_model_contingent'); + } + + /** + * @throws \Doctrine\DBAL\DBALException + */ + public function clearTable() + { + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_contingent'); + } + + /** + * @throws \Doctrine\DBAL\DBALException + */ + public function copyTable() + { + $this->db->executeStatement('INSERT INTO tx_epproducts_domain_model_contingent SELECT * FROM tx_epproducts_domain_model_contingent_temp'); + } + + /** + * @throws \Doctrine\DBAL\DBALException + */ + public function clearTempTable() + { + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_contingent_temp'); + } + + /** + * @return int + */ + protected function parseContingent(\SimpleXMLElement $xmlContingent) + { + $hotelCode = (string) $xmlContingent['code']; + $sql = 'SELECT h.uid uid + FROM tx_epproducts_domain_model_hotel h + WHERE h.deleted = 0 AND h.hidden = 0 AND h.code = :code AND h.pid = :pid + LIMIT 0,1'; + $hotelData = $this->db->fetchAssociative($sql, ['code' => $hotelCode, 'pid' => $this->hotelsPid]); + + // Skip import in case hotel can't be found + if (false === $hotelData) { + $this->logger->warning('hotel not found', ['code' => $hotelCode]); + + return 0; + } + + $hotelData['busProId'] = (string) $xmlContingent['idbuspro']; + $hotelData['code'] = (string) $xmlContingent['code']; + + if ($xmlContingent->idbuspro_kontingent_aus) { + $linkedProductId = (int) $xmlContingent->idbuspro_kontingent_aus; + $path = GeneralUtility::getFileAbsFileName(self::XML_PATH); + if (null === $xmlContingent = $this->loadXmlFile(sprintf('%s/HotelZimmer_%d.xml', $path, $linkedProductId))) { + return 0; + } + } + + if (empty($xmlContingent->kapazitaeten)) { + $this->logger->warning('hotel has no contingents', ['code' => $hotelCode]); + + return 0; + } + + $roomTypes = $this->parseRoomTypes($xmlContingent->unterbringungen); + + return $this->parseRooms($xmlContingent->kapazitaeten, $hotelData, $roomTypes); + } + + /** + * @param \SimpleXMLElement $xmlTypes + * + * @return array + */ + protected function parseRoomTypes($xmlTypes) + { + $types = []; + foreach ($xmlTypes->unterbringung as $xmlType) { + $idBusPro = (string) $xmlType['idbuspro']; + $code = (string) $xmlType['code']; + $types[] = [ + 'idBusPro' => $idBusPro, + 'link' => isset($xmlType['idbuspro_kontingent_aus']) ? (string) $xmlType['idbuspro_kontingent_aus'] : null, + 'pax' => (int) $xmlType['pax_max'], + 'code' => $code, + 'label' => (string) $xmlType['zimmerbezeichnung'], + 'ctrl' => self::ROOMCODE_STATUS === $code, + ]; + } + + return $types; + } + + /** + * @param \SimpleXMLElement|\SimpleXMLElement[] $xmlDates + * + * @return int + */ + protected function parseRooms(\SimpleXMLElement $xmlDates, array $hotelData, array $roomTypes) + { + $dateCount = 0; + foreach ($xmlDates->kapazitaet as $xmlDate) { + $date = \DateTime::createFromFormat('d.m.Y', (string) $xmlDate['termin']); + foreach ($roomTypes as $roomType) { + $roomCode = $roomType['code']; + if (in_array($roomCode, $this->ignoredRoomTypes, true)) { + continue; + } + $roomTypeBusProId = $roomType['link'] ?: $roomType['idBusPro']; + $xpath = sprintf('zimmerliste/zimmer[@idbuspro="%d"]', $roomTypeBusProId); + $xmlRoom = $xmlDate->xpath($xpath)[0]; + if (null === $xmlRoom) { + continue; + } + $this->calculateContingent($xmlRoom, $roomType, $date, $hotelData); + } + ++$dateCount; + } + + return $dateCount; + } + + protected function calculateContingent(\SimpleXMLElement $xmlRoom, array $roomType, \DateTime $date, array $hotelData): void + { + $availablePax = 0; + $capacity = 0; + $status = Contingent::STATUS_OK; + $isControlRoom = true === $roomType['ctrl']; + if ($isControlRoom && 'A' === (string) $xmlRoom['status']) { + $status = Contingent::STATUS_ONREQUEST; + } elseif ($isControlRoom && 'S' === (string) $xmlRoom['status']) { + $status = Contingent::STATUS_BLOCKED; + } else { + $capacity = (int) $xmlRoom['kontingent'] * (int) $roomType['pax']; + $availableRooms = (int) $xmlRoom['frei'] > 0 ? (int) $xmlRoom['frei'] : 0; + $availablePax = $availableRooms * $roomType['pax']; + } + $minPrice = (int) $xmlRoom['abpreis']; + $minNights = (int) $xmlRoom['abpreis_naechte']; + $additionalNightMinPrice = (int) $xmlRoom['abpreis_verlaengerung']; + $additionalNightMinNights = (int) $xmlRoom['abpreis_verlaengerung_naechte']; + $roomLabel = $roomType['label']; + $this->db->insert( + 'tx_epproducts_domain_model_contingent_temp', + [ + 'pid' => $this->contingentsPid, + 'hotel' => $hotelData['uid'], + 'hotel_code' => $hotelData['code'], + 'hotel_bus_pro_id' => $hotelData['busProId'], + 'room_code' => $roomType['code'], + 'room_label' => $roomLabel, + 'pax' => $capacity, + 'available' => $availablePax, + 'status' => $status, + 'date' => $date->format('Y-m-d'), + 'min_price' => $minPrice, + 'min_nights' => $minNights, + 'additional_night_min_price' => $additionalNightMinPrice, + 'additional_night_min_nights' => $additionalNightMinNights, + ] + ); + } +} diff --git a/packages/ep_products/Classes/Service/DateImportService.php b/packages/ep_products/Classes/Service/DateImportService.php new file mode 100644 index 0000000..210a5ed --- /dev/null +++ b/packages/ep_products/Classes/Service/DateImportService.php @@ -0,0 +1,970 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use Doctrine\DBAL\Connection; +use EP\EpProducts\Domain\Model\Room; +use EP\EpProducts\Traits\DbConnectionTrait; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; +use TYPO3\CMS\Extbase\Service\CacheService; + +class DateImportService implements SingletonInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + use DbConnectionTrait; + + const CODE_PSEUDOPRICE = 'PDGS'; + const CODE_BABYROOM = 'Baby'; + const LABEL_BUS = 'Busfahrt'; + const LABEL_TRAIN = 'Zugfahrt'; + const STATUS_AVAILABLE = 'Frei'; + const STATUS_ON_REQUEST = 'Anfrage'; + const STATUS_STOP = 'Buchungsstop'; + + /** + * BusPro ids of the selection groups whose selections make up the included + * services. Matched by id rather than by label, so a group renamed upstream + * does not silently drop out of services_included. + */ + const SELECTION_GROUP_SKIPASS = 7; + const SELECTION_GROUP_OTHER = 8; + const SELECTION_GROUP_BOARD = 71; + const SELECTION_GROUP_ACCOMMODATION = 85; + const SELECTION_GROUP_PROGRAMME = 86; + const SELECTION_GROUP_COURSES = 87; + const SELECTION_GROUP_EVENTS = 89; + const SELECTION_GROUP_TRANSPORTATION = 90; + + /** + * Group 'Kunde' also carries trip types (Wochenreise, Midweekreise, ...), so + * group travel keys on the selection, not on the group's presence. Every + * date should carry either 'Gruppenkunde' or 'Endkunde'. + */ + const SELECTION_GROUP_CUSTOMER = 96; + const SELECTION_CUSTOMER_GROUP = 1418; + const SELECTION_CUSTOMER_END = 1419; + + /** + * Fallback for dates the office has not tagged with either selection: these + * trip type markers in front of the travel code's trailing date mean + * end-customer travel. + */ + const END_CUSTOMER_CODE_PATTERN = '/(?:MP|WE|KT|PM|MW|FA)\d{6}$/'; + + /** + * 'Leistungen Personal' occurs in no export so far, so its id is unknown and + * it is still matched by label. Register its id once the warning fires. + */ + const SELECTION_GROUP_LABEL_PERSONNEL = 'Leistungen Personal'; + + /** + * @var ConfigurationManagerInterface + */ + protected $configurationManager; + + /** + * @var int + */ + protected $pid = 0; + + /** + * @var array + */ + protected $hotelMappings = []; + + /** + * @var Connection + */ + protected $db; + + /** + * @var CacheService + */ + protected $cacheService; + + /** + * @var ProductImportSourceFactory + */ + protected $importSourceFactory; + + /** + * @var RoomTypeResolver + */ + protected $roomTypeResolver; + + /** + * @var array + */ + static protected $cache = [ + 'datesAdded' => [], + ]; + + /** + * @param ConfigurationManagerInterface $configurationManager + * @param CacheService $cacheService + * @param ProductImportSourceFactory $importSourceFactory + * @param RoomTypeResolver $roomTypeResolver + */ + public function __construct( + ConfigurationManagerInterface $configurationManager, + CacheService $cacheService, + ProductImportSourceFactory $importSourceFactory, + RoomTypeResolver $roomTypeResolver + ) { + $this->configurationManager = $configurationManager; + $this->cacheService = $cacheService; + $this->importSourceFactory = $importSourceFactory; + $this->roomTypeResolver = $roomTypeResolver; + + $settings = GeneralUtility::removeDotsFromTS( + $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + + if (isset($settings['plugin']['tx_epproducts']['persistence']['storagePid'])) { + $this->pid = $settings['plugin']['tx_epproducts']['persistence']['storagePid']; + } elseif (isset($settings['module']['tx_epproducts']['persistence']['storagePid'])) { + $this->pid = $settings['module']['tx_epproducts']['persistence']['storagePid']; + } + } + + /** + * @param iterable $rows one row per travel date × hotel, in the shape + * of GET /api/exports/products on MyE&P + * @return int + * @throws \Doctrine\DBAL\DBALException + */ + public function import(iterable $rows): int + { + $this->db = $this->getDbConnection(); + $this->getHotelMappings(); + $this->createTempTables(); + $this->logger->info('starting product import'); + $dateCount = $this->importProducts($rows); + $this->clearTables(); + $this->copyTables(); + $this->clearTempTables(); + $this->cacheService->clearPageCache(); + + return $dateCount; + } + + public function isImportRequired(): bool + { + return $this->importSourceFactory->get()->isNewImportAvailable(); + } + + /** + * Groups the rows by product and travel date, in the order they first + * appear, because a product code can map to more than one TYPO3 product and + * each of them imports all of the product's dates. + * + * @param iterable $rows + * @return int + */ + protected function importProducts(iterable $rows): int + { + $products = []; + foreach ($rows as $row) { + $code = $row['product']['code']; + if (!isset($products[$code])) { + $products[$code] = ['busProId' => (int) $row['product']['busProId'], 'dates' => []]; + } + $products[$code]['dates'][(int) $row['date']['busProId']][] = $row; + } + + $dateCount = 0; + foreach ($products as $code => $product) { + $dateCount += $this->importProduct((string) $code, $product['busProId'], $product['dates']); + } + + return $dateCount; + } + + /** + * Recreated on every run, so the temp tables always follow the schema of the + * live tables (a column type change would otherwise survive in them) and a + * crashed import cannot leave rows behind for the next one. + */ + public function createTempTables(): void + { + $this->db->executeStatement('DROP TABLE IF EXISTS tx_epproducts_domain_model_date_temp'); + $this->db->executeStatement('DROP TABLE IF EXISTS tx_epproducts_domain_model_room_temp'); + $this->db->executeStatement('CREATE TABLE tx_epproducts_domain_model_date_temp LIKE tx_epproducts_domain_model_date'); + $this->db->executeStatement('CREATE TABLE tx_epproducts_domain_model_room_temp LIKE tx_epproducts_domain_model_room'); + } + + public function clearTables(): void + { + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_date'); + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_room'); + } + + public function copyTables(): void + { + $this->db->executeStatement('INSERT INTO tx_epproducts_domain_model_date SELECT * FROM tx_epproducts_domain_model_date_temp'); + $this->db->executeStatement('INSERT INTO tx_epproducts_domain_model_room SELECT * FROM tx_epproducts_domain_model_room_temp'); + } + + public function clearTempTables(): void + { + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_date_temp'); + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_room_temp'); + } + + /** + * @param string $code + * @param int $busProId + * @param array[][] $dates rows grouped by travel date + * @return int + */ + protected function importProduct(string $code, int $busProId, array $dates): int + { + $dateCount = 0; + + $sql = ' + SELECT p.uid uid, p.pid pid, p.daytrip daytrip, p.hide_booking_button, p.important important, + p.name product_name, p.searchable product_searchable, p.detail_page product_detail_page, + p.keywords product_keywords, p.teaser product_teaser, p.feature product_feature, + p.subline product_subline, p.country country, p.region region, p.city city, p.path_segment slug, + p.season season, p.concept concept, + cnt.name country_name, cnt.code country_code, cnt.keywords country_keywords, r.name region_name, + r.keywords region_keywords, r.feature region_feature, + c.name city_name, c.keywords city_keywords, + con.name concept_name, con.code concept_code, con.sorting concept_sorting, + con.popup_text concept_popup_text + FROM tx_epproducts_domain_model_product p + INNER JOIN tx_epproducts_domain_model_country cnt ON p.country = cnt.uid + INNER JOIN tx_epproducts_domain_model_region r ON p.region = r.uid + INNER JOIN tx_epproducts_domain_model_city c ON p.city = c.uid + INNER JOIN tx_epproducts_domain_model_concept con ON p.concept = con.uid + WHERE p.deleted = 0 AND p.hidden = 0 AND p.code = :code + '; + + $products = $this->db->fetchAllAssociative($sql, ['code' => $code]); + + // Skip import in case product can't be found + if (count($products) === 0) { + $this->logger->warning('product not found or daytrip', ['code' => $code]); + return 0; + } + foreach ($products as $product) { + $this->db->update( + 'tx_epproducts_domain_model_product', + ['bus_pro_id' => $busProId], + ['uid' => $product['uid']] + ); + $product['bus_pro_id'] = $busProId; + $product['product_fact'] = $this->getTopFactForRecord('tx_epproducts_product_fact_mm', $product['uid']); + $product['region_fact'] = $this->getTopFactForRecord('tx_epproducts_region_fact_mm', $product['region']); + foreach ($dates as $rows) { + $dateCount += $this->importDate($rows, $product); + } + } + + return $dateCount; + } + + /** + * @param array[] $rows one per hotel of the travel date + * @param array $product + * @return int + */ + protected function importDate(array $rows, array $product): int + { + $dateCount = 0; + + // Selection groups are date-level, so any hotel row tells. + $this->checkCustomerSelection(reset($rows)); + + foreach ($rows as $row) { + $date = $this->createDateRecord($product, $row); + + $hotelCode = $row['hotel']['code']; + $key = $hotelCode . $date['date_start']; + + // Import daytrip dates only once per hotel as room information is taken + // from contingents + if ($product['daytrip'] && \in_array($key, static::$cache['datesAdded'], true)) { + continue; + } + + if ($this->parseHotel($row, $date) !== false) { + $this->db->insert( + 'tx_epproducts_domain_model_date_temp', + $date + ); + $dateUid = $this->db->lastInsertId(); + $this->db->update( + 'tx_epproducts_domain_model_room_temp', + ['date' => $dateUid], + ['date' => '0'] + ); + $dateCount++; + } + + static::$cache['datesAdded'][] = $key; + } + $this->db->update( + 'tx_epproducts_domain_model_product', + ['dates' => $dateCount], + ['uid' => $product['uid']] + ); + return $dateCount; + } + + /** + * @param array $product + * @param array $row + * @return array + */ + protected function createDateRecord(array $product, array $row): array + { + $dateStart = \DateTime::createFromFormat('Y-m-d', $row['date']['from']); + $dateEnd = \DateTime::createFromFormat('Y-m-d', $row['date']['to']); + $nights = $dateStart->diff($dateEnd)->format('%d'); + + $busProId = (int) $row['date']['busProId']; + $code = (string) $row['date']['code']; + + $date = [ + 'pid' => $this->pid, + 'product' => $product['uid'], + 'product_pid' => $product['pid'], + 'product_name' => $product['product_name'], + 'product_fact' => $product['product_fact'], + 'product_searchable' => $product['product_searchable'], + 'product_detail_page' => $product['product_detail_page'], + 'product_keywords' => $product['product_keywords'], + 'product_feature' => $product['product_feature'], + 'product_teaser' => $product['product_teaser'], + 'product_subline' => $product['product_subline'], + 'product_slug' => $product['slug'], + 'country' => $product['country'], + 'country_name' => $product['country_name'], + 'country_code' => $product['country_code'], + 'country_keywords' => $product['country_keywords'], + 'region' => $product['region'], + 'region_name' => $product['region_name'], + 'region_fact' => $product['region_fact'], + 'region_keywords' => $product['region_keywords'], + 'region_feature' => $product['region_feature'], + 'city' => $product['city'], + 'city_name' => $product['city_name'], + 'city_keywords' => $product['city_keywords'], + 'concept' => $product['concept'], + 'concept_name' => $product['concept_name'], + 'concept_code' => $product['concept_code'], + 'concept_popup_text' => $product['concept_popup_text'], + 'concept_sorting' => $product['concept_sorting'], + 'season' => empty($product['season']) ? 'w' : $product['season'], + 'date_start' => $dateStart->format('Y-m-d'), + 'date_end' => $dateEnd->format('Y-m-d'), + 'nights' => $nights, + 'bus_pro_id' => $busProId, + 'code' => $code, + 'daytrip' => $product['daytrip'], + 'hide_booking_button' => $product['hide_booking_button'], + 'important' => $product['important'], + ]; + + $includedAccommodation = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_ACCOMMODATION); + $includedSkiPass = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_SKIPASS); + $includedBoard = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_BOARD); + $includedTransportation = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_TRANSPORTATION); + $includedProgramme = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_PROGRAMME); + $includedPersonnel = $this->parsePersonnelSelectionGroup($row['selectionGroups']); + $includedEvents = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_EVENTS); + $includedOther = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_OTHER); + $includedCourses = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_COURSES); + + $includedServices = [ + ...$includedAccommodation, + ...$includedSkiPass, + ...$includedTransportation, + ...$includedBoard, + ...$includedProgramme, + ...$includedCourses, + ...$includedEvents, + ...$includedPersonnel, + ...$includedOther, + ]; + + $date['services_included'] = serialize($includedServices); + $date['courses_included'] = serialize($includedCourses); + + if ($includedBoard) { + $date['board'] = implode(', ', $includedBoard); + $date['board_bus_pro_id'] = key($includedBoard); + } + + $skiPassIncluded = (int) (count($includedSkiPass) > 0); + $date['skipass_included'] = $skiPassIncluded; + + $date['group_travel'] = (int) $this->isGroupTravel($row); + + $servicesTransportation = $this->parseService($row['transportServices']); + $discount = $this->determineDiscount($servicesTransportation); + $date['discount'] = $discount; + + $this->processBusConditions($servicesTransportation, $date); + + $servicesTransportationOptional = []; + if (array_key_exists('BUS', $servicesTransportation)) { + $servicesTransportationOptional = array_filter($servicesTransportation['BUS'], function ($service) { + return $service['price'] !== 0; + }); + } + if (array_key_exists('BAH', $servicesTransportation)) { + $servicesTransportationOptional = array_merge( + $servicesTransportationOptional, + $servicesTransportation['BAH'] + ); + } + + if ($discount) { + $servicesTransportationOptional[] = [ + 'label' => 'Rabatt bei Eigenanreise', + 'price' => number_format($discount, 2, ',', '.'), + ]; + } + + $servicesGeneral = $this->parseService($row['dateServices']); + + $combinedServices = []; + + if (count($servicesTransportationOptional)) { + $this->addServices(['BUS' => $servicesTransportationOptional], $combinedServices); + } + + if (count($servicesGeneral)) { + $this->addServices($servicesGeneral, $combinedServices); + } + + foreach ($combinedServices as $key => $list) + { + uasort($list, function ($a, $b) { + return strcmp($a['label'], $b['label']); + }); + $combinedServices[$key] = $list; + } + $date['services_general'] = serialize($combinedServices); + + $pickups = []; + foreach ($row['pickups'] as $pickup) { + $busProId = (int) $pickup['busProId']; + if ($busProId === 0) { + continue; + } + $pickupDate = $dateStart->format('d.m.Y'); + $pickupTime = ''; + if ($pickup['datetime'] !== null) { + [$pickupDate, $pickupTime] = $this->parsePickupDateTime($pickup['datetime']); + } + $pickups[] = [ + 'bus_pro_id' => $busProId, + 'city' => $pickup['city'], + 'street' => $pickup['street'], + // A pickup without a stated price has always been shown as included. + 'price' => $pickup['price'] === null ? 0.0 : (float) $pickup['price'], + 'date' => $pickupDate, + 'time' => $pickupTime, + ]; + } + usort($pickups, function ($a, $b) { + if (empty($a['date']) || empty($b['date']) || empty($a['time']) || empty($b['time'])) { + return 0; + } + + $timeStampA = \DateTimeImmutable::createFromFormat('d.m.Y H:i', $a['date'].' '.$a['time'])->getTimestamp(); + $timeStampB = \DateTimeImmutable::createFromFormat('d.m.Y H:i', $b['date'].' '.$b['time'])->getTimestamp(); + + return $timeStampA <=> $timeStampB; + }); + $date['pickups'] = serialize($pickups); + + $date['guide'] = serialize($row['guide'] ?? []); + + return $date; + } + + /** + * @param array $row + * @param array $date + * @return bool + */ + protected function parseHotel(array $row, array &$date): bool + { + $code = $row['hotel']['code']; + $busProId = (int) $row['hotel']['busProId']; + + if (!array_key_exists($code, $this->hotelMappings)) { + $this->logger->warning('hotel not found', ['code' => $code]); + + return false; + } + + $hotel = $this->hotelMappings[$code]; + $hotel['fact'] = $this->getTopFactForRecord('tx_epproducts_hotel_fact_mm', $hotel['uid']); + + $services = []; + $generalServices = unserialize($date['services_general']); + $hotelServices = $this->parseService($row['hotelServices'], true); + $this->addServices($generalServices, $services); + $this->addServices($hotelServices, $services); + foreach ($services as $key => $list) { + uasort($list, function ($a, $b) { + return strcmp($a['label'], $b['label']); + }); + $services[$key] = $list; + } + + $this->parseRooms($row, $hotel['uid'], $date, $services); + + $date['hotel'] = $hotel['uid']; + $date['hotel_code'] = $hotel['code']; + $date['hotel_bus_pro_id'] = $busProId; + $date['hotel_fact'] = $hotel['fact']; + $date['hotel_name'] = $hotel['name']; + $date['hotel_type'] = $hotel['type']; + $date['hotel_keywords'] = $hotel['keywords']; + $date['hotel_header_title'] = $hotel['header_title']; + $date['hotel_category'] = $hotel['category']; + $date['hotel_teaser'] = $hotel['teaser']; + $date['hotel_detail_page'] = $hotel['detail_page']; + $date['earlybird'] = $hotel['earlybird']; + $date['new'] = $hotel['new']; + + return true; + } + + /** + * @param array $row + * @param int $hotelUid + * @param array $date + * @param array $services + */ + protected function parseRooms(array $row, $hotelUid, array &$date, array $services): void + { + $minPriceOverall = 0.0; + $minPriceAvailable = 0.0; + $minPax = 0; + $availableTotal = 0; + $boardCode = ''; + $count = 0; + + if ($row['pseudoPrice'] !== null) { + $date['pseudo_price'] = (float) $row['pseudoPrice']; + } + + foreach ($row['rooms'] as $roomRow) { + $code = $roomRow['code']; + $label = $roomRow['label']; + $roomMinPax = (int) $roomRow['minPax']; + $pax = (int) $roomRow['maxPax']; + + $available = (int) $roomRow['available']; + $price = (float) $roomRow['price']; + + // Skip rooms with zero price _and_ zero availability. + if (!$available && !$price) { + continue; + } + + $roomType = $this->roomTypeResolver->resolve($code, $label, $pax, $roomMinPax); + + if ($pax < 1 && $roomMinPax < 1) { + $this->logger->warning('room without occupancy', ['code' => $code, 'zimmertext' => $label]); + } + + if ($this->roomTypeResolver->needsReview($code, $label, $pax)) { + $this->logger->warning( + 'single-occupancy room without single-use marker', + ['code' => $code, 'zimmertext' => $label] + ); + } + + if (0 === $minPax || $roomMinPax < $minPax) { + $minPax = $roomMinPax; + } + + $availableTotal += $available * $pax; + + // Determine lowest price of available rooms + if ($code !== static::CODE_BABYROOM && $available > 0 && ($minPriceAvailable === 0.0 || $price < $minPriceAvailable)) { + $minPriceAvailable = $price; + } + + // Determine lowest overall price + if ($code !== static::CODE_BABYROOM && ($minPriceOverall === 0.0 || $price < $minPriceOverall)) { + $minPriceOverall = $price; + } + + // Determine status + $sourceStatus = $roomRow['status']; + + if (static::STATUS_STOP === $sourceStatus && $price) { + $status = Room::ROOM_STATUS_FULLY_BOOKED; + } elseif (static::STATUS_ON_REQUEST === $sourceStatus) { + $status = Room::ROOM_STATUS_ON_REQUEST; + } else { + $status = Room::ROOM_STATUS_AVAILABLE; + } + + $boardCode = $roomRow['boardCode']; + + $room = [ + 'pid' => $this->pid, + 'date' => 0, + 'hotel' => $hotelUid, + 'bus_pro_id' => (int) $roomRow['busProId'], + 'code' => $code, + 'type' => $roomType, + 'name' => $label, + 'pax' => $pax, + 'nights' => (int) $roomRow['nights'], + 'price' => $price, + 'available' => $available * $pax, + 'status' => $status, + 'services' => serialize($services), + ]; + + $this->db->insert( + 'tx_epproducts_domain_model_room_temp', + $room + ); + + $count++; + } + + $date['board_code'] = $boardCode; + $date['min_price'] = $minPriceAvailable ?: $minPriceOverall; + $date['available'] = $availableTotal; + $date['rooms'] = $count; + $date['min_pax'] = $minPax; + } + + /** + * @param array $services + * @return float + */ + protected function determineDiscount(array $services): float + { + foreach ($services as $key => $section) { + foreach ($section as $service) { + $price = $this->parseFormattedPrice($service['price']); + if ($price < 0) { + return $price; + } + } + } + + return 0.0; + } + + /** + * @param array[] $services a service list of a row + * @param bool $skipWithoutPrice + * @return array + */ + protected function parseService(array $services, bool $skipWithoutPrice = false): array + { + $data = []; + foreach ($services as $service) { + // Skip transportation service direction 'back' + if ($service['direction'] === 'RUECK') { + continue; + } + + // Skip services with status 'Buchungsstop' + if ($service['status'] === static::STATUS_STOP) { + continue; + } + + // Skip services of subcategory 'EIN' + $type = $service['subType']; + if ($type === 'EIN') { + continue; + } + + // Force usage of new types + if ($type === 'SKI' || $type === 'SNO') { + $type = 'VER'; + } + + // A service without a stated price has always been shown as included. + $price = $service['price'] === null ? 0.0 : (float) $service['price']; + + if ($skipWithoutPrice && $price === 0.0) { + continue; + } + + $label = $service['label']; + if ($type === 'BUS') { + $label = static::LABEL_BUS; + } elseif ($type === 'BAH') { + $label = static::LABEL_TRAIN; + } + + if (!array_key_exists($type, $data)) { + $data[$type] = []; + } + + $data[$type][(int) $service['busProId']] = [ + 'idBusPro' => (int) $service['busProId'], + 'label' => $label, + 'price' => number_format($price, 2, ',', '.'), + ]; + } + + ksort($data); + + return $data; + } + + /** + * @param array[] $selectionGroups + * @param int $busProId + * @return array + */ + protected function parseSelectionGroup(array $selectionGroups, int $busProId): array + { + $data = []; + foreach ($selectionGroups as $group) { + if ((int) $group['busProId'] === $busProId) { + $this->addSelections($group['selections'], $data); + } + } + + ksort($data); + + return $data; + } + + /** + * @param array[] $selectionGroups + * @return array + */ + protected function parsePersonnelSelectionGroup(array $selectionGroups): array + { + $data = []; + foreach ($selectionGroups as $group) { + if ($group['label'] !== static::SELECTION_GROUP_LABEL_PERSONNEL) { + continue; + } + $this->logger->warning( + 'selection group matched by label, register its id', + ['label' => $group['label'], 'idbuspro' => (int) $group['busProId']] + ); + $this->addSelections($group['selections'], $data); + } + + ksort($data); + + return $data; + } + + /** + * 'Gruppenkunde' and 'Endkunde' decide. A date carrying neither is judged by + * its travel code. + * + * @param array $row + * @return bool + */ + protected function isGroupTravel(array $row): bool + { + $customer = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_CUSTOMER); + $isGroup = isset($customer[static::SELECTION_CUSTOMER_GROUP]); + $isEnd = isset($customer[static::SELECTION_CUSTOMER_END]); + if ($isGroup || $isEnd) { + return $isGroup; + } + + return !preg_match(static::END_CUSTOMER_CODE_PATTERN, (string) $row['date']['code']); + } + + /** + * Reports dates whose customer selection is missing or contradictory, along + * with what they import as. + * + * @param array $row + */ + protected function checkCustomerSelection(array $row): void + { + $customer = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_CUSTOMER); + $isGroup = isset($customer[static::SELECTION_CUSTOMER_GROUP]); + $isEnd = isset($customer[static::SELECTION_CUSTOMER_END]); + if ($isGroup !== $isEnd) { + return; + } + + $this->logger->warning( + $isGroup + ? 'date has both Gruppenkunde and Endkunde selected, importing as group travel' + : 'date has neither Gruppenkunde nor Endkunde selected, falling back to travel code, importing as ' + . ($this->isGroupTravel($row) ? 'group travel' : 'end-customer travel'), + ['product' => $row['product']['code'], 'date' => $row['date']['code']] + ); + } + + /** + * @param array[] $selections + * @param array $data + */ + protected function addSelections(array $selections, array &$data): void + { + foreach ($selections as $selection) { + $data[(int) $selection['busProId']] = $selection['label']; + } + } + + /** + * @param array $services + * @param array $date + */ + protected function processBusConditions(array $services, array &$date): void + { + if (!array_key_exists('BUS', $services)) { + return; + } + + foreach ($services['BUS'] as $service) { + $price = $this->parseFormattedPrice($service['price']); + $date['bus_available'] = 1; + $date['bus_price'] = $price; + $date['bus_included'] = (int) ($price === 0.0); + } + } + + /** + * A pickup time is the local date and time as BusPro states it + * ("2027-03-28T02:45:00"), taken over as is. An ISO-8601 time with an + * offset is still accepted and converted to Europe/Berlin, which cannot + * express a time inside the daylight saving gap. + * + * @param string $datetime + * @return string[] date as d.m.Y and time as H:i + */ + protected function parsePickupDateTime(string $datetime): array + { + if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::\d{2}(?:\.\d+)?)?$/', $datetime, $parts)) { + return [$parts[3] . '.' . $parts[2] . '.' . $parts[1], $parts[4] . ':' . $parts[5]]; + } + + $pickupDateTime = (new \DateTimeImmutable($datetime))->setTimezone(new \DateTimeZone('Europe/Berlin')); + + return [$pickupDateTime->format('d.m.Y'), $pickupDateTime->format('H:i')]; + } + + /** + * Reads back a price parseService() formatted for display ("1.234,50"). + * + * @param string $price + * @return float + */ + protected function parseFormattedPrice(string $price): float + { + return (float) str_replace(['.', ','], ['', '.'], $price); + } + + protected function getHotelMappings(): void + { + $sql = ' + SELECT u.code code, u.uid uid, h.name name, h.type type, + h.earlybird earlybird, h.new new, h.low_contingent low_contingent, + h.keywords keywords, h.header_title header_title, + h.category category, h.teaser teaser, h.detail_page detail_page + FROM ( + SELECT hotel.code code, hotel.uid uid + FROM tx_epproducts_domain_model_hotel hotel + WHERE hotel.code != "" AND hotel.deleted = 0 AND hotel.hidden = 0 + UNION SELECT matchcode.code code, matchcode.entity uid + FROM tx_epproducts_domain_model_matchcode matchcode + INNER JOIN tx_epproducts_domain_model_hotel matchcode_hotel ON matchcode.entity = matchcode_hotel.uid + WHERE matchcode.deleted = 0 AND matchcode.hidden = 0 + AND matchcode_hotel.deleted = 0 AND matchcode_hotel.hidden = 0 + ORDER BY code + ) u + LEFT JOIN tx_epproducts_domain_model_hotel h ON u.uid = h.uid + '; + $rows = $this->db->fetchAllAssociative($sql); + foreach ($rows as $row) { + $this->hotelMappings[$row['code']] = [ + 'uid' => $row['uid'], + 'code' => $row['code'], + 'type' => $row['type'], + 'name' => $row['name'], + 'earlybird' => $row['earlybird'], + 'new' => $row['new'], + 'low_contingent' => $row['low_contingent'], + 'keywords' => $row['keywords'], + 'header_title' => $row['header_title'], + 'category' => $row['category'], + 'teaser' => $row['teaser'], + 'detail_page' => $row['detail_page'], + ]; + } + } + + /** + * @param string $table + * @param int $uid + * @return string + */ + protected function getTopFactForRecord($table, $uid): string + { + $sql = ' + SELECT f.name + FROM ' . $table . ' mm + INNER JOIN tx_epproducts_domain_model_fact f ON mm.uid_foreign = f.uid + WHERE mm.uid_local = :uid + ORDER BY mm.sorting + LIMIT 0,1 + '; + + return $this->db->fetchOne($sql, ['uid' => $uid]); + } + + /** + * @param array $services + * @param array $list + */ + protected function addServices(array $services, array &$list): void + { + foreach ($services as $key => $section) { + if (!array_key_exists($key, $list)) { + $list[$key] = []; + } + $list[$key] += $section; + } + } + +} diff --git a/packages/ep_products/Classes/Service/DateService.php b/packages/ep_products/Classes/Service/DateService.php new file mode 100644 index 0000000..95e54eb --- /dev/null +++ b/packages/ep_products/Classes/Service/DateService.php @@ -0,0 +1,529 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Date; +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\DateRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Utility\BookingUrlUtility; +use Symfony\Component\OptionsResolver\OptionsResolver; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; + +class DateService implements SingletonInterface +{ + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @var FilterService + */ + protected $filterService; + + /** + * @var DateRepository + */ + protected $dateRepository; + + /** + * @var UriBuilder + */ + protected $uriBuilder; + + /** + * @var string[] + */ + protected $serviceSections = [ + 'BUS' => 'Anreise', + 'VPF' => 'Verpflegung', + 'SPA' => 'Skipass', + 'KUR' => 'Kurse', + 'VER' => 'Verleih', + 'SON' => 'Sonstiges', + 'LVS' => 'Leihmaterial-Versicherung', + 'PAR' => 'Parkplatz', + ]; + + private $serviceSectionsMapping = [ + 'VE2' => 'VER', + 'VE3' => 'VER', + 'VE4' => 'VER', + 'VE5' => 'VER', + 'VE6' => 'VER', + 'VE7' => 'VER', + 'VE8' => 'VER', + ]; + + /** + * @param ProductRepository $productRepository + * @param DateRepository $dateRepository + * @param FilterService $filterService + * @param UriBuilder $uriBuilder + */ + public function __construct + ( + ProductRepository $productRepository, + DateRepository $dateRepository, + FilterService $filterService, + UriBuilder $uriBuilder + ) + { + $this->productRepository = $productRepository; + $this->filterService = $filterService; + $this->dateRepository = $dateRepository; + $this->uriBuilder = $uriBuilder; + } + + /** + * @param array $options + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getPriceTable(array $options): array + { + $resolver = new OptionsResolver(); + $this->configurePriceTableOptions($resolver); + $resolvedOptions = $resolver->resolve($options); + + /** @var Product $product */ + $product = $resolvedOptions['product']; + $hotel = $resolvedOptions['hotel']; + $filterSettings = $resolvedOptions['filterSettings']; + $date = $resolvedOptions['date']; + $myEpUrl = $resolvedOptions['myEpUrl']; + $paCode = $resolvedOptions['paCode']; + + if ($filterSettings === null) { + $filterSettings = $this->filterService->getNormalizedFilterSettings(); + } + + $nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true); + + return $this->preprocessPriceTable([ + 'priceTableData' => $this->productRepository->getPricetable($product, $hotel, $filterSettings, $date), + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + 'showBookingButton' => false === $product->getHideBookingButton(), + 'nonBookableDateUids' => $nonBookableDateUids, + ]); + } + + protected function configurePriceTableOptions(OptionsResolver $resolver) + { + $resolver->setRequired(['product', 'hotel', 'myEpUrl']); + $resolver->setDefined(['filterSettings', 'date', 'paCode']); + $resolver->setDefaults([ + 'date' => null, + 'sortByRoom' => true, + ]); + $resolver->setAllowedTypes('product', Product::class); + $resolver->setAllowedTypes('hotel', Hotel::class); + $resolver->setAllowedTypes('date', [Date::class, 'null']); + $resolver->setAllowedTypes('sortByRoom', 'bool'); + } + + public function getDatesTable(array $options): array + { + $resolver = new OptionsResolver(); + $this->configureDatesTableOptions($resolver); + $resolvedOptions = $resolver->resolve($options); + + /** @var Product $product */ + $product = $resolvedOptions['product']; + $hotel = $resolvedOptions['hotel']; + $filterSettings = $resolvedOptions['filterSettings']; + + if ($filterSettings === null) { + $filterSettings = $this->filterService->getNormalizedFilterSettings(); + } + + return $this->productRepository->getAvailableDates($product, $hotel, $filterSettings); + } + + /** + * @param OptionsResolver $resolver + */ + protected function configureDatesTableOptions(OptionsResolver $resolver) + { + $resolver->setDefined(['filterSettings']); + $resolver->setRequired(['product', 'hotel']); + $resolver->setAllowedTypes('product', Product::class); + $resolver->setAllowedTypes('hotel', Hotel::class); + } + + public function getEventPriceTable(Product $product, int $maxPax, string $myEpUrl): array + { + return $this->preprocessEventPriceTable([ + 'priceTableData' => $this->productRepository->getEventPriceTable($product), + 'maxPax' => $maxPax, + 'myEpUrl' => $myEpUrl, + ]); + } + + public function preprocessPriceTable(array $options): array + { + $resolver = new OptionsResolver(); + $resolver->setDefined(['paCode', 'showBookingButton', 'nonBookableDateUids']); + $resolver->setRequired(['priceTableData', 'myEpUrl']); + $resolver->setDefault('showBookingButton', true); + $resolvedOptions = $resolver->resolve($options); + + $priceTable = [ + 'available' => [], + 'unavailable' => [], + ]; + + $priceTableData = $resolvedOptions['priceTableData']; + $myEpUrl = $resolvedOptions['myEpUrl']; + $paCode = $resolvedOptions['paCode']; + $nonBookableDateUids = $resolvedOptions['nonBookableDateUids']; + + foreach ($priceTableData as $row) { + $showBookingButton = $resolvedOptions['showBookingButton'] && !in_array($row['dateBusProId'], $nonBookableDateUids); + + $dateStart = new \DateTime($row['dateStart']); + $dateEnd = new \DateTime($row['dateEnd']); + $bookingUrl = BookingUrlUtility::generateUrl([ + 'dateId' => $row['dateBusProId'], + 'hotelId' => $row['hotelBusProId'], + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + 'returnPageUid' => $row['productDetailPage'] ?? null, + ]); + $optionalServices = $this->preprocessOptionalServices(unserialize($row['roomOptionalServices'], ['allowed_classes' => false])); + + $key = $row['roomAvailable'] && $row['roomPrice'] ? 'available' : 'unavailable'; + + $priceTable[$key][] = [ + 'dateStart' => $dateStart->format('d.m.Y'), + 'dateEnd' => $dateEnd->format('d.m.Y'), + 'season' => $row['season'], + 'summer' => $row['season'] === 's', + 'busIncluded' => (bool) $row['dateBusIncluded'], + 'skiPassIncluded' => (bool) $row['dateSkiPassIncluded'], + 'groupTravel' => (bool) $row['dateGroupTravel'], + 'roomUid' => $row['roomUid'], + 'roomName' => $row['roomName'], + 'available' => $row['roomAvailable'] && $row['roomPrice'], + 'minPrice' => $row['roomPrice'], + 'busPrice' => $row['busPrice'], + 'discount' => $row['roomDiscount'], + 'nights' => $row['roomNights'], + 'optionalServices' => $optionalServices, + 'hasOptionalServices' => count($optionalServices) > 0, + 'dateBusProId' => $row['dateBusProId'], + 'hotelBusProId' => $row['hotelBusProId'], + 'roomBusProId' => $row['roomBusProId'], + 'bookingUrl' => $bookingUrl, + 'showBookingButton' => $showBookingButton, + ]; + } + + return $priceTable; + } + + public function preprocessOptionalServices(array $servicesFlat): array + { + $processed = []; + + foreach ($servicesFlat as $key => $services) { + $servicesWithCosts = array_filter($services, function ($service) { + return '0,00' !== $service['price']; + }); + if (0 === count($servicesWithCosts)) { + continue; + } + if (array_key_exists($key, $this->serviceSectionsMapping)) { + $key = $this->serviceSectionsMapping[$key]; + } + if (!array_key_exists($key, $processed)) { + $processed[$key] = [ + 'label' => $this->serviceSections[$key], + 'services' => [], + ]; + } + $processed[$key]['services'] += $servicesWithCosts; + } + + return $processed; + } + + public function preprocessDatesTable(array $options): array + { + $resolver = new OptionsResolver(); + $resolver->setDefined(['paCode', 'showBookingButton', 'nonBookableDateUids']); + $resolver->setRequired(['dateTableData', 'myEpUrl']); + $resolver->setDefault('showBookingButton', true); + $resolver->setDefault('nonBookableDateUids', []); + $resolvedOptions = $resolver->resolve($options); + + $dateTable = []; + + $dateTableData = $resolvedOptions['dateTableData']; + $myEpUrl = $resolvedOptions['myEpUrl']; + $paCode = $resolvedOptions['paCode']; + $nonBookableDateUids = $resolvedOptions['nonBookableDateUids']; + + foreach ($dateTableData as $row) { + // Default booking button status is determined by the product + $showBookingButton = $resolvedOptions['showBookingButton']; + // Check if booking button is disabled for this date + if (true === in_array($row['dateBusProId'], $nonBookableDateUids)) { + $showBookingButton = false; + } + + $dateStart = new \DateTime($row['dateStart']); + $dateEnd = new \DateTime($row['dateEnd']); + $bookingUrl = BookingUrlUtility::generateUrl([ + 'dateId' => $row['dateBusProId'], + 'hotelId' => $row['hotelBusProId'], + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + 'returnPageUid' => $row['productDetailPage'] ?? null, + ]); + $dateTable[] = [ + 'dateUid' => $row['dateUid'], + 'dateStart' => $dateStart->format('d.m.y'), + 'dateEnd' => $dateEnd->format('d.m.y'), + 'minPrice' => $row['dateMinPrice'], + 'pseudoPrice' => $row['datePseudoPrice'], + 'busPrice' => $row['dateBusPrice'] ?? 0, + 'discount' => $row['dateDiscount'] ?? 0, + 'nights' => $row['dateNights'], + 'busIncluded' => (bool) $row['dateBusIncluded'], + 'skiPassIncluded' => (bool) $row['dateSkiPassIncluded'], + 'groupTravel' => (bool) $row['dateGroupTravel'], + 'servicesIncluded' => unserialize($row['dateServicesIncluded'], ['allowed_classes' => false]), + 'servicesGeneral' => unserialize($row['dateServicesGeneral'], ['allowed_classes' => false]), + 'season' => $row['season'], + 'summer' => $row['season'] === 's', + 'available' => $row['available'], + 'unavailable' => $row['available'] === 0, + 'bookingUrl' => $bookingUrl, + 'showBookingButton' => $showBookingButton, + ]; + } + + return $dateTable; + } + + public function preprocessEventPriceTable(array $options): array + { + $resolver = new OptionsResolver(); + $resolver->setDefined('paCode'); + $resolver->setRequired(['priceTableData', 'myEpUrl']); + $resolver->setDefaults([ + 'maxPax' => 99, + ]); + $resolvedOptions = $resolver->resolve($options); + + $priceTableData = $resolvedOptions['priceTableData']; + $maxPax = $resolvedOptions['maxPax']; + $myEpUrl = $resolvedOptions['myEpUrl']; + $paCode = $resolvedOptions['paCode']; + + $priceTable = [ + Hotel::CATEGORY_STANDARD => [], + Hotel::CATEGORY_COMFORT => [], + Hotel::CATEGORY_DELUXE => [], + Hotel::CATEGORY_SUPER_DELUXE => [], + ]; + + foreach ($priceTableData as $row) + { + // Skip hotels in price table that are not shown as teasers + if (!$row['hotelShowAsTeaser']) { + continue; + } + // Set default hotel category to avoid issues with invalid data + if (0 === $categoryUid = (int)$row['hotelCategory']) { + $categoryUid = Hotel::CATEGORY_STANDARD; + } + $hotelUid = $row['hotelUid']; + + if (!array_key_exists($hotelUid, $priceTable[$categoryUid])) { + + $priceTable[$categoryUid][$hotelUid] = [ + 'category' => $categoryUid, + 'hotelName' => $row['hotelName'], + 'hotelShortName' => $row['hotelShortName'], + 'hotelTitle' => $row['hotelTitle'], + 'hotelDetailPageUid' => $row['hotelMappingDetailPageUid'] ?? $row['hotelDetailPageUid'], + 'bookingUrl' => BookingUrlUtility::generateUrl([ + 'dateId' => $row['dateBusProId'], + 'hotelId' => $row['hotelBusProId'], + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + 'returnPageUid' => $row['productDetailPage'] ?? null, + ]), + 'rooms' => [], + ]; + } + + $roomPax = $row['roomPax']; + if ($roomPax > $maxPax) { + $roomPax = $maxPax; + } + + $entry =& $priceTable[$categoryUid][$hotelUid]['rooms']; + $isUndersubscription = 1 === preg_match('/mit \d{1,2} Personen/', $row['roomName']); + + // Add new entry or replace possible existing entry that is not + // bookable with current record in case of matching category + // and pax + if (!array_key_exists($roomPax, $entry)) { + $entry[$roomPax] = [ + 'price' => $row['roomPrice'], + 'available' => $row['available'] && $row['roomPrice'], + 'undersubscription' => $isUndersubscription, + ] ; + } else { + $lastEntry = $entry[$roomPax]; + if (!$lastEntry['available'] || ($lastEntry['available'] && $row['available'] && ($lastEntry['price'] > $row['roomPrice']))) { + $entry[$roomPax] = [ + 'price' => $row['roomPrice'], + 'available' => $row['available'] && $row['roomPrice'], + 'undersubscription' => $isUndersubscription, + ]; + } + } + } + + // Sort by category + foreach ($priceTable as $category => $rows) + { + uasort($rows, function ($a, $b) { + return strcmp( + $a['rooms'][Hotel::CATEGORY_SUPER_DELUXE]['price'], + $b['rooms'][Hotel::CATEGORY_SUPER_DELUXE]['price'] + ); + }); + $priceTable[$category] = $rows; + } + + return $priceTable; + } + + public function hasIncludedServices(array $tableData): bool + { + foreach ($tableData as $row) { + if ($row['busIncluded'] || $row['skiPassIncluded']) { + return true; + } + } + + return false; + } + + /** + * Group included services across bookable dates. + * + * Returns services that appear in every supplied date and services that + * appear in at least one supplied date but not all of them. + * + * @param array $dates + * @return array{allDates: array, someDates: array} + */ + public function groupIncludedServices(array $dates): array + { + $serviceCounts = []; + $serviceOrder = []; + $dateCount = count($dates); + + foreach ($dates as $date) { + $dateServices = $date['servicesIncluded'] ?? []; + if (!is_array($dateServices)) { + continue; + } + + $seenInDate = []; + foreach ($dateServices as $service) { + if (!is_string($service) || '' === trim($service)) { + continue; + } + + if (isset($seenInDate[$service])) { + continue; + } + $seenInDate[$service] = true; + + if (!array_key_exists($service, $serviceCounts)) { + $serviceCounts[$service] = 0; + $serviceOrder[] = $service; + } + + $serviceCounts[$service]++; + } + } + + $groupedServices = [ + 'allDates' => [], + 'someDates' => [], + ]; + + if (0 === $dateCount) { + return $groupedServices; + } + + foreach ($serviceOrder as $service) { + $count = $serviceCounts[$service] ?? 0; + if ($dateCount === $count) { + $groupedServices['allDates'][] = $service; + } elseif ($count > 0) { + $groupedServices['someDates'][] = $service; + } + } + + sort($groupedServices['someDates'], SORT_NATURAL | SORT_FLAG_CASE); + + return $groupedServices; + } + + public function checkSurcharcheOrDiscount(array $tableData): array + { + $data = [ + 'surcharge' => false, + 'discount' => false, + ]; + + foreach ($tableData as $row) { + if ($row['busPrice'] && $row['busPrice'] > 0) { + $data['surcharge'] = true; + } + if ($row['discount'] && $row['discount'] < 0) { + $data['discount'] = true; + } + } + + return $data; + } +} diff --git a/packages/ep_products/Classes/Service/EmailService.php b/packages/ep_products/Classes/Service/EmailService.php new file mode 100644 index 0000000..94bfda0 --- /dev/null +++ b/packages/ep_products/Classes/Service/EmailService.php @@ -0,0 +1,90 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use Symfony\Component\Mime\Address; +use Symfony\Component\OptionsResolver\OptionsResolver; +use TYPO3\CMS\Core\Mail\FluidEmail; +use TYPO3\CMS\Core\Mail\Mailer; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; + +class EmailService implements SingletonInterface +{ + /** + * @var array + */ + protected $settings; + + public function __construct(ConfigurationManagerInterface $manager) + { + $settings = GeneralUtility::removeDotsFromTS( + $manager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + $this->settings = $settings['plugin']['tx_eptheme']; + } + + /** + * @param array $options + * @param array $variables + * @return bool + */ + public function send(array $options, array $variables) + { + $resolvedOptions = $this->resolveOptions($options); + + /** @var FluidEmail $email */ + $email = GeneralUtility::makeInstance(FluidEmail::class); + $email + ->to(new Address($resolvedOptions['toEmail'], $resolvedOptions['toName'])) + ->from(new Address($resolvedOptions['fromEmail'], $resolvedOptions['fromName'])) + ->subject($resolvedOptions['subject']) + ->setTemplate($resolvedOptions['templateName']) + ->assignMultiple($variables) + ; + + GeneralUtility::makeInstance(Mailer::class)->send($email); + } + + /** + * @param array $options + * @return array + */ + protected function resolveOptions(array $options) + { + $optionsResolver = new OptionsResolver(); + $optionsResolver->setDefined(['toEmail', 'toName', 'subject', 'templateName', 'fromEmail', 'fromName']); + $optionsResolver->setRequired(['toEmail', 'toName', 'subject', 'templateName']); + $optionsResolver->setDefaults([ + 'fromEmail' => $this->settings['settings']['contactFormToEmail'], + 'fromName' => $this->settings['settings']['contactFormToName'], + ]); + + return $optionsResolver->resolve($options); + } +} diff --git a/packages/ep_products/Classes/Service/FilterService.php b/packages/ep_products/Classes/Service/FilterService.php new file mode 100644 index 0000000..ffbfba6 --- /dev/null +++ b/packages/ep_products/Classes/Service/FilterService.php @@ -0,0 +1,342 @@ +setDefaults([ + 'productsPid' => [], + 'priceRanges' => [], + 'countryUids' => [], + 'regionUids' => [], + 'cityUids' => [], + 'conceptUids' => [], + 'boardTypes' => [], + 'hotelTypes' => [], + 'roomTypes' => [], + 'hotelUid' => 0, + 'productUid' => 0, + 'dateFrom' => null, + 'dateTo' => null, + 'nights' => 0, + 'pax' => 0, + 'bus' => null, + ]); + foreach (['hotelUid', 'productUid', 'nights', 'pax'] as $key) { + $optionsResolver->setNormalizer($key, function (Options $options, $value) { + return null !== $value ? (int)$value : null; + }); + } + foreach (['productsPid', 'priceRanges', 'countryUids', 'regionUids', 'cityUids', 'conceptUids', 'boardTypes', 'hotelTypes', 'roomTypes'] as $key) { + $optionsResolver->setNormalizer($key, function (Options $options, $value) { + if (empty($value)) { + return []; + } + return array_map('intval', (array) $value); + }); + } + $optionsResolver->setNormalizer('bus', function (Options $options, $value) { + return null !== $value ? (bool)$value : null; + }); + + $filterSettings = $optionsResolver->resolve($filterSettings); + + if (count($forcedConceptUids) > 0) { + array_push($filterSettings['conceptUids'], ...$forcedConceptUids); + } + + $typoScriptSettings = $this->getTypoScriptSettings(); + if (is_array($typoScriptSettings) && isset($typoScriptSettings['productsPid'])) { + $filterSettings['productsPid'] = GeneralUtility::trimExplode(',', $typoScriptSettings['productsPid'], true); + } + + return $filterSettings; + } + + public function getFilterSettingsFromSearchParams(array $searchParams, array $forcedConceptUids = []): array + { + $filterSettings = []; + + if ($searchParams['destinationType'] && $searchParams['destinationUid']) { + switch ($searchParams['destinationType']) { + case SearchResultService::DESTINATION_TYPE_COUNTRY: + $filterSettings['countryUids'] = [ $searchParams['destinationUid'] ]; + break; + case SearchResultService::DESTINATION_TYPE_REGION: + $filterSettings['regionUids'] = [ $searchParams['destinationUid'] ]; + break; + case SearchResultService::DESTINATION_TYPE_CITY: + $filterSettings['cityUids'] = [ $searchParams['destinationUid'] ]; + break; + case SearchResultService::DESTINATION_TYPE_CONCEPT: + $filterSettings['conceptUids'] = [ $searchParams['destinationUid'] ]; + break; + } + } + if ($searchParams['dateFrom']) { + $filterSettings['dateFrom'] = $searchParams['dateFrom']; + } + if ($searchParams['dateTo']) { + $filterSettings['dateTo'] = $searchParams['dateTo']; + } + if ($searchParams['priceRange']) { + $filterSettings['priceRanges'] = [ $searchParams['priceRange'] ]; + } + if ($searchParams['pax']) { + $filterSettings['pax'] = $searchParams['pax']; + } + if ($searchParams['nights']) { + $filterSettings['nights'] = $searchParams['nights']; + } + + return $this->getNormalizedFilterSettings($filterSettings, $forcedConceptUids); + } + + public function getFilterSettingsFromStaticSearchParams(array $staticSearchParams): array + { + $filterSettings = []; + + if (!empty($staticSearchParams['country'])) { + $filterSettings['countryUids'] = [ $staticSearchParams['country'] ]; + } + if (!empty($staticSearchParams['region'])) { + $filterSettings['regionUids'] = [ $staticSearchParams['region'] ]; + } + if (!empty($staticSearchParams['concept'])) { + $filterSettings['conceptUids'] = GeneralUtility::intExplode(',', $staticSearchParams['concept'], true); + } + if (!empty($staticSearchParams['hotel'])) { + $filterSettings['hotelUid'] = $staticSearchParams['hotel']; + } + if (!empty($staticSearchParams['product'])) { + $filterSettings['productUid'] = $staticSearchParams['product']; + } + if (!empty($staticSearchParams['hotel_types'])) { + $filterSettings['hotelTypes'] = GeneralUtility::intExplode(',', $staticSearchParams['hotel_types'], true); + } + if (!empty($staticSearchParams['bus'])) { + $filterSettings['bus'] = $staticSearchParams['bus']; + } + if (!empty($staticSearchParams['date_from']) && $staticSearchParams['date_from'] !== '0000-00-00') { + $filterSettings['dateFrom'] = $staticSearchParams['date_from']; + } + if (!empty($staticSearchParams['date_to']) && $staticSearchParams['date_to'] !== '0000-00-00') { + $filterSettings['dateTo'] = $staticSearchParams['date_to']; + } + if (!empty($staticSearchParams['pax']) && $staticSearchParams['pax'] > 0) { + $filterSettings['pax'] = $staticSearchParams['pax']; + } + + return $this->getNormalizedFilterSettings($filterSettings); + } + + public function collectFilterOptionsFromSearchResult(SearchResult $searchResult): FilterOptions + { + $priceRanges = []; + $busAvailable = false; + $nights = []; + $concepts = []; + $destinations = []; + $hotelTypes = []; + $roomTypes = []; + $boardTypes = []; + $dateMin = $dateMax = null; + + foreach ($searchResult->getData() as $row) { + $concept = $row['concept']; + if (!array_key_exists($concept['uid'], $concepts)) { + $concepts[$concept['sorting']] = [ + 'uid' => $concept['uid'], + 'name' => $concept['name'], + 'code' => $concept['code'], + ]; + } + foreach ($row['items'] as $item) { + if (null === $dateMin || $item['minDateStart'] < $dateMin) { + $dateMin = $item['minDateStart']; + } + if (null === $dateMax || $item['maxDateEnd'] > $dateMax) { + $dateMax = $item['maxDateEnd']; + } + if ((bool) $item['busAvailable'] === true) { + $busAvailable = true; + } + foreach ($item['nights'] as $nightsOption) { + $duration = (int) $nightsOption <= 4 ? self::DURATION_SHORT : self::DURATION_LONG; + if (!\in_array($duration, $nights, false)) { + $nights[] = $duration; + } + } + $itemDestinations = $item['filterMetadata']['destinations'] ?? [[ + 'country' => $item['country'], + 'region' => $item['region'], + ]]; + foreach ($itemDestinations as $itemDestination) { + $country = $itemDestination['country']; + $region = $itemDestination['region']; + if (!array_key_exists($country['uid'], $destinations)) { + $destinations[$country['uid']] = [ + 'country' => [ + 'uid' => $country['uid'], + 'label' => $country['name'], + 'code' => $country['code'], + ], + 'regions' => [], + ]; + } + if (!array_key_exists($region['uid'], $destinations[$country['uid']]['regions'])) { + $destinations[$country['uid']]['regions'][$region['uid']] = [ + 'uid' => $region['uid'], + 'label' => $region['name'], + ]; + } + } + foreach ($item['filterMetadata']['hotelTypes'] ?? [$item['hotel']['type']] as $hotelType) { + if (!\in_array($hotelType, $hotelTypes, false)) { + $hotelTypes[] = $hotelType; + } + } + foreach ($item['rooms'] as $roomType) { + // Type 0 is not a room (Baby) and must never become a filter option: + // it has no label, so it would render as an empty checkbox and leave + // a hole in the option list. + if ((int) $roomType === Room::ROOM_TYPE_UNCLASSIFIED) { + continue; + } + if (!\in_array($roomType, $roomTypes, false)) { + $roomTypes[] = $roomType; + } + } + foreach ($item['filterMetadata']['boardTypes'] ?? [[ + 'uid' => $item['boardUid'], + 'label' => $item['board'], + ]] as $boardType) { + if (!\in_array($boardType, $boardTypes, false)) { + $boardTypes[] = $boardType; + } + } + foreach (FilterOptions::$priceRangeOptions as $index => $priceRange) { + if (array_key_exists($index, $priceRanges)) { + continue; + } + [$min, $max] = $priceRange; + if ($item['minPrice'] >= $min && $item['minPrice'] < $max) { + $range = FilterOptions::$priceRangeOptions[$index]; + $priceRanges[$index] = [ + 'uid' => $index, + 'min' => $range[0], + 'max' => $range[1], + 'formatted' => $this->formatPriceRange($range[0], $range[1]), + ]; + } + } + } + } + + foreach ($destinations as $key => $value) { + usort($value['regions'], function ($a, $b) { + return strcmp($a['label'], $b['label']); + }); + $destinations[$key] = $value; + } + sort($destinations); + sort($nights); + sort($roomTypes); + ksort($priceRanges); + + usort($boardTypes, function ($a, $b) { + return strcmp($a['label'], $b['label']); + }); + + $filterOptions = new FilterOptions(); + $filterOptions->setPriceRanges($priceRanges); + $filterOptions->setDateFrom($dateMin); + $filterOptions->setDateTo($dateMax); + $filterOptions->setConcepts($concepts); + $filterOptions->setDestinations($destinations); + $filterOptions->setHotelTypes($hotelTypes); + $filterOptions->setRoomTypes($roomTypes); + $filterOptions->setBoardTypes($boardTypes); + $filterOptions->setBusAvailable($busAvailable); + $filterOptions->setNights($nights); + + $filterOptions->setResultCount($searchResult->getTotal()); + + return $filterOptions; + } + + public function formatPriceRange(int $min, int $max): string + { + if ($min === 1) { + return sprintf('bis %d €', $max); + } + if ($max === 999) { + return sprintf('ab %d €', $min); + } + + return sprintf('%d - %d €', $min, $max); + } + + public function getFiltersettingsFromSettings(array $settings): array + { + $filterSettings = []; + + if (isset($settings['regionUid']) && (int)$settings['regionUid'] > 0) { + $filterSettings['regionUids'] = [ $settings['regionUid'] ]; + } elseif (isset($settings['countryUid']) && (int) $settings['countryUid'] > 0) { + $filterSettings['countryUids'] = [ $settings['countryUid'] ]; + } elseif (isset($settings['hotelUid']) && (int) $settings['hotelUid'] > 0) { + $filterSettings['hotelUid'] = $settings['hotelUid']; + } + $conceptUids = GeneralUtility::intExplode(',', $settings['conceptUid'], true); + if ($conceptUids) { + $filterSettings['conceptUids'] = $conceptUids; + } + if (isset($settings['dateFrom']) && (int) $settings['dateFrom'] > 0) { + $dateFrom = new \DateTime(); + $dateFrom->setTimestamp($settings['dateFrom']); + $filterSettings['dateFrom'] = $dateFrom->format('Y-m-d'); + } + if (isset($settings['dateTo']) && (int) $settings['dateTo'] > 0) { + $dateTo = new \DateTime(); + $dateTo->setTimestamp($settings['dateTo']); + $filterSettings['dateTo'] = $dateTo->format('Y-m-d'); + } + if (isset($settings['busAvailable']) && (bool) $settings['busAvailable'] === true) { + $filterSettings['bus'] = true; + } + if (array_key_exists('hotelTypes', $settings)) { + $hotelTypes = GeneralUtility::intExplode(',', $settings['hotelTypes'], true); + $filterSettings['hotelTypes'] = $hotelTypes; + } + + return $this->getNormalizedFilterSettings($filterSettings); + } + + protected function getTypoScriptSettings(): ?array + { + /** @var ConfigurationManager $configurationManager */ + $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); + $settings = $configurationManager + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); + + return $settings ? $settings['plugin.']['tx_epproducts.']['settings.'] : null; + } + +} diff --git a/packages/ep_products/Classes/Service/FilterSettingsEncoder.php b/packages/ep_products/Classes/Service/FilterSettingsEncoder.php new file mode 100644 index 0000000..11f86a0 --- /dev/null +++ b/packages/ep_products/Classes/Service/FilterSettingsEncoder.php @@ -0,0 +1,112 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class FilterSettingsEncoder +{ + /** + * @return array + */ + protected function getMapping() + { + return [ + [ 'countryUids', 'array' ], + [ 'regionUids', 'array' ], + [ 'cityUids', 'array' ], + [ 'conceptUids', 'array' ], + [ 'hotelUid', 'string' ], + [ 'productUid', 'string' ], + [ 'nights', 'string' ], + [ 'pax', 'string' ], + [ 'boardTypes', 'array' ], + [ 'hotelTypes', 'array' ], + [ 'roomTypes', 'array' ], + [ 'dateFrom', 'date' ], + [ 'dateTo', 'date' ], + [ 'bus', 'string' ], + ]; + } + + public function encode(array $filterSettings): string + { + $encoded = []; + foreach ($this->getMapping() as $property) { + [$name, $type] = $property; + if (!isset($filterSettings[$name])) { + $encoded[] = ''; + continue; + } + $value = $filterSettings[$name]; + if ($type === 'array') { + $encoded[] = implode(';', $value); + } elseif ($type === 'date') { + $encoded[] = $value ?? ''; + } else { + $encoded[] = preg_replace('/[\r\n]/', '', $value); + } + } + + return implode('|', $encoded); + } + + /** + * Decodes an encoded filter settings string. + * + * The format is strictly positional, so a string whose field count does not + * match the mapping cannot be interpreted: every field after a missing one + * would be read into the wrong setting. Such input is rejected outright + * rather than silently producing a shifted - and therefore wrong - result. + * + * @param string $data + * @return array the decoded settings, or an empty array if $data is malformed + */ + public function decode(string $data): array + { + $mapping = $this->getMapping(); + $values = explode('|', $data); + + if (count($values) !== count($mapping)) { + return []; + } + + $decoded = []; + foreach ($mapping as $index => $property) + { + [$name, $type] = $property; + $value = $values[$index]; + if ($type === 'array') { + $value = GeneralUtility::trimExplode(';', $value, true); + } + $decoded[$name] = $value; + } + + return $decoded; + } +} diff --git a/packages/ep_products/Classes/Service/GroupsSwissService.php b/packages/ep_products/Classes/Service/GroupsSwissService.php new file mode 100644 index 0000000..4b13942 --- /dev/null +++ b/packages/ep_products/Classes/Service/GroupsSwissService.php @@ -0,0 +1,267 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\BpnConnect\ApiClient; +use EP\EpProducts\Domain\Repository\HotelRepository; +use League\Period\Period; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use TYPO3\CMS\Core\SingletonInterface; + +class GroupsSwissService implements SingletonInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + + /** + * @var HotelRepository + */ + protected $hotelRepository; + + /** + * @var ApiClient + */ + protected $apiClient; + + /** + * @var HttpClient + */ + protected $httpClient; + + /** + * @param HotelRepository $hotelRepository + * @param ApiClient $apiClient + */ + public function __construct(HotelRepository $hotelRepository, ApiClient $apiClient) + { + $this->hotelRepository = $hotelRepository; + $this->apiClient = $apiClient; + $this->httpClient = HttpClient::create(); + } + + public function publishContingents(int $hotelUid, int $houseNumber, string $apiKey): bool + { + $range = Period::after(new \DateTime('now'), '1 year'); + + $hotel = $this->hotelRepository->findByIdentifier($hotelUid); + + if (null === $hotel) { + $this->logger->error(sprintf('Hotel with uid %d not found', $hotelUid)); + return false; + } + + $hotelCode = trim((string) $hotel->getCode()); + if ('' === $hotelCode) { + $this->logger->error(sprintf('Hotel with uid %d has no hotel code', $hotelUid)); + return false; + } + + try { + $calendar = $this->apiClient->getCalendar( + $hotelCode, + \DateTime::createFromImmutable($range->getStartDate()), + \DateTime::createFromImmutable($range->getEndDate()->sub(new \DateInterval('P1D'))) + ); + $calendarRows = isset($calendar['data']) && is_array($calendar['data']) ? $calendar['data'] : []; + } catch (\Throwable $e) { + $this->logger->error(sprintf('No dates found for hotel with uid %d', $hotelUid), ['exception' => $e]); + return false; + } + + $this->resetChart($range, $apiKey, $houseNumber); + + $this->generateChart( + $calendarRows, + $apiKey, + $houseNumber + ); + + return true; + } + + /** + * @param Period $range + * @param string $apiKey + * @param string $houseNumber + */ + protected function resetChart(Period $range, $apiKey, $houseNumber): void + { + $item = [ + 'from' => $range->getStartDate(), + 'to' => $range->getEndDate(), + ]; + + $this->addItem($item, 0, $apiKey, $houseNumber); + } + + /** + * @param array $calendarRows + * @param string $apiKey + * @param int $houseNumber + */ + protected function generateChart(array $calendarRows, $apiKey, $houseNumber): void + { + usort($calendarRows, function (array $left, array $right): int { + return strcmp((string) ($left['date'] ?? ''), (string) ($right['date'] ?? '')); + }); + + $item = null; + $itemStatus = null; + $lastDate = null; + + foreach ($calendarRows as $row) { + if (!isset($row['date'])) { + continue; + } + + $currentDate = new \DateTimeImmutable($row['date']); + $currentStatus = $this->normalizeStatus((string) ($row['status'] ?? 'OK')); + + if (null !== $lastDate && $currentDate > $lastDate->add(new \DateInterval('P1D'))) { + if (null !== $item) { + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); + $item = null; + $itemStatus = null; + } + } + + if ('OK' === $currentStatus) { + if (null !== $item) { + $item['to'] = $lastDate; + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); + $item = null; + $itemStatus = null; + } + $lastDate = $currentDate; + continue; + } + + if (null === $item) { + $item = [ + 'from' => $currentDate, + 'to' => $currentDate, + ]; + $itemStatus = $currentStatus; + } elseif ($itemStatus !== $currentStatus) { + $item['to'] = $lastDate; + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); + $item = [ + 'from' => $currentDate, + 'to' => $currentDate, + ]; + $itemStatus = $currentStatus; + } else { + $item['to'] = $currentDate; + } + + $lastDate = $currentDate; + } + + if (null !== $item) { + $item['to'] = $lastDate; + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); + } + } + + /** + * @param array $item + * @param int $status + * @param string $apiKey + * @param int $houseNumber + */ + protected function addItem(array $item, $status, $apiKey, $houseNumber): void + { + $from = $item['from']->format('Y-m-d'); + $to = $item['to']->format('Y-m-d'); + + $this->logger->info(sprintf( + 'Adding status %d in %s-%s for %s', + $status, + $from, + $to, + $houseNumber + )); + + try { + $response = $this + ->httpClient + ->request('GET', 'https://www.groups.swiss/api/1.0/json.php', [ + 'query' => [ + 'apiKey' => $apiKey, + 'method' => 'setAvailabilityChart', + 'house' => $houseNumber, + 'from' => $from, + 'till' => $to, + 'state' => $status, + ], + ]) + ; + $this->logger->info($response->getContent()); + } catch (TransportExceptionInterface $e) { + $this->logger->error(sprintf('A problem occured: %s', $e->getMessage())); + } + } + + /** + * @param string $status + * @return string + */ + protected function normalizeStatus(string $status): string + { + $status = strtoupper(trim($status)); + if ('ONREQUEST' === $status) { + return 'ON_REQUEST'; + } + + if (in_array($status, [ 'OK', 'ON_REQUEST', 'BLOCKED' ], true)) { + return $status; + } + + return 'OK'; + } + + /** + * @param string $status + * @return int + */ + protected function translateStatusToGroupsSwissState(string $status): int + { + $status = $this->normalizeStatus($status); + + if ('ON_REQUEST' === $status) { + return 1; + } + + if ('BLOCKED' === $status) { + return 2; + } + + return 0; + } +} diff --git a/packages/ep_products/Classes/Service/HotelDataService.php b/packages/ep_products/Classes/Service/HotelDataService.php new file mode 100644 index 0000000..29eeba9 --- /dev/null +++ b/packages/ep_products/Classes/Service/HotelDataService.php @@ -0,0 +1,274 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\HotelRepository; +use TYPO3\CMS\Core\SingletonInterface; + +class HotelDataService implements SingletonInterface +{ + + /** + * @var \EP\EpProducts\Domain\Repository\HotelRepository + */ + protected $hotelRepository; + + /** + * @var HotelImageService + */ + protected $hotelImageService; + + /** + * @param HotelRepository $hotelRepository + * @param HotelImageService $hotelImageService + */ + public function __construct(HotelRepository $hotelRepository, HotelImageService $hotelImageService) + { + $this->hotelRepository = $hotelRepository; + $this->hotelImageService = $hotelImageService; + } + + /** + * @param Product $product + * + * @return array + */ + public function getList(Product $product) + { + $hotelData = $this->hotelRepository->getListByProduct($product->getUid()); + return $this->preprocessHotelList($hotelData); + } + + /** + * @param int $hotelUid + * @return array + */ + public function getTeaser($hotelUid) + { + $hotelData = $this->hotelRepository->getSingleByUid($hotelUid); + + if ($hotelData === false) { + $hotelData = $this->hotelRepository->getDateIndependentSingleByUid($hotelUid); + $hotelData['available'] = -1; // TODO: Needs improvement. '-1' actually means: no price/date info available + } + + $hotelCategory = array_key_exists($hotelData['hotelCategory'], Hotel::$categoryLabels) ? + Hotel::$categoryLabels[$hotelData['hotelCategory']] : null; + + $dateStart = new \DateTime($hotelData['dateStart']); + $dateEnd = new \DateTime($hotelData['dateEnd']); + + return [ + 'minDateStart' => $dateStart, + 'maxDateEnd' => $dateEnd, + 'minPrice' => $hotelData['minPrice'], + 'pseudoPrice' => $hotelData['pseudoPrice'], + 'available' => $hotelData['available'], + 'detailPage' => $hotelData['hotelDetailPage'], + 'name' => $hotelData['hotelName'], + 'teaser' => $hotelData['hotelTeaser'], + 'category' => $hotelCategory, + 'images' => $this->hotelImageService->getImages($hotelUid), + 'country' => [ + 'name' => $hotelData['country'], + 'code' => $hotelData['countryCode'], + ], + 'region' => [ + 'name' => $hotelData['regionName'], + ] + ]; + } + + /** + * @param array $criteria + * @return array + */ + public function getTeasersByCriteria(array $criteria) + { + $teaserData = $this->hotelRepository->getListByCriteria($criteria); + + return $this->preprocessTeaserData($teaserData); + } + + /** + * @param array $criteria + * @param array $excludedHotelUids + * @return array + */ + public function getDateIndependentTeasersByCriteria(array $criteria, array $excludedHotelUids = []) + { + $teaserData = $this->hotelRepository->getDateIndependentListByCriteria($criteria, $excludedHotelUids); + + return $this->preprocessTeaserData($teaserData); + } + + /** + * @param array $teaserData + * @return array + */ + protected function preprocessTeaserData(array $teaserData) + { + $teasers = []; + foreach ($teaserData as $row) + { + $hotelUid = $row['hotelUid']; + + $hotelCategory = Hotel::$categoryLabels[$row['hotelCategory']] ?? null; + + if (!array_key_exists($hotelUid, $teasers)) { + $teasers[$hotelUid] = [ + 'minDateStart' => null, + 'maxDateEnd' => null, + 'minPrice' => null, + 'pseudoPrice' => $row['pseudoPrice'], + 'available' => 0, + 'detailPage' => $row['hotelDetailPage'], + 'name' => $row['hotelName'], + 'teaser' => $row['hotelTeaser'], + 'category' => $hotelCategory, + 'images' => $this->hotelImageService->getImages($hotelUid), + 'country' => [ + 'name' => $row['country'], + 'code' => $row['countryCode'], + ], + 'region' => [ + 'name' => $row['regionName'], + ] + ]; + } + + $teaser =& $teasers[$hotelUid]; + $teaser['available'] += $row['available']; + + $dateStart = new \DateTime($row['dateStart']); + $dateEnd = new \DateTime($row['dateEnd']); + + if ($teaser['minDateStart'] === null || $teaser['minDateStart'] > $dateStart) { + $teaser['minDateStart'] = $dateStart; + } + + if ($teaser['maxDateEnd'] === null || $teaser['maxDateEnd'] < $dateEnd) { + $teaser['maxDateEnd'] = $dateEnd; + } + + if ($teaser['minPrice'] === null || $teaser['minPrice'] > $row['minPrice']) { + $teaser['minPrice'] = $row['minPrice']; + } + } + + return $teasers; + } + + /** + * @param array $hotelData + * + * @return array + */ + public function preprocessHotelList(array $hotelData) + { + $hotels = []; + foreach ($hotelData as $row) + { + if (!array_key_exists($row['hotelUid'], $hotels)) { + + $hotelCategory = Hotel::$categoryLabels[$row['hotelCategory']] ?? null; + + $hotels[$row['hotelUid']] = [ + 'hotel' => [ + 'uid' => $row['hotelUid'], + 'name' => $row['hotelName'], + 'teaser' => $row['hotelTeaser'], + 'category' => $hotelCategory, + 'images' => $this->hotelImageService->getImages($row['hotelUid']), + 'fact' => $row['hotelFact'], + ], + 'country' => [ + 'name' => $row['country'], + 'code' => $row['countryCode'], + ], + 'available' => 0, + 'board' => $row['board'], + 'earlybird' => $row['earlybird'], + 'new' => $row['new'], + 'lowContingent' => $row['lowContingent'], + 'servicesIncluded' => unserialize($row['servicesIncluded']), + 'minDateStart' => null, + 'maxDateEnd' => null, + 'minPrice' => null, + 'dates' => [], + 'nights' => [], + ]; + } + + $hotel =& $hotels[$row['hotelUid']]; + + $hotel['available'] += $row['available']; + + if (!\in_array($row['nights'], $hotel['nights'], false)) { + $hotel['nights'][] = $row['nights']; + } + + if (!\in_array($row['dateStart'], $hotel['dates'], false)) { + $hotel['dates'][] = $row['dateStart']; + } + + if ($hotel['minDateStart'] === null + || $hotel['minDateStart'] > $row['dateStart']) { + $hotel['minDateStart'] = $row['dateStart']; + } + + if ($hotel['maxDateEnd'] === null + || $hotel['maxDateEnd'] < $row['dateEnd']) { + $hotel['maxDateEnd'] = $row['dateEnd']; + } + + if ($hotel['minPrice'] === null + || $hotel['minPrice'] > $row['minPrice']) { + $hotel['minPrice'] = $row['minPrice']; + } + } + + uasort($hotels, function ($a, $b) { + // Set price to a very high value to sort hotels + // that are not bookable to end of list + if ($a['minPrice'] === null || !$a['available']) { + $a['minPrice'] = 9999; + } + if ($b['minPrice'] === null || !$b['available']) { + $b['minPrice'] = 9999; + } + + return strcmp($a['minPrice'], $b['minPrice']); + }); + + return $hotels; + } + +} diff --git a/packages/ep_products/Classes/Service/HotelImageService.php b/packages/ep_products/Classes/Service/HotelImageService.php new file mode 100644 index 0000000..7d27fbb --- /dev/null +++ b/packages/ep_products/Classes/Service/HotelImageService.php @@ -0,0 +1,58 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class HotelImageService extends AbstractImageService +{ + /** + * @var string + */ + protected $table = 'tx_epproducts_domain_model_hotel'; + + /** + * @var string + */ + protected $field = 'teaser_images'; + + /** + * @var array + */ + protected $imageSizes = [ + 's' => [ + 'width' => '220', + 'height' => '130c-100', + ], + 'm' => [ + 'width' => '260', + 'height' => '200c-100', + ], + 'l' => [ + 'width' => '280', + 'height' => '220c-100', + ] + ]; +} diff --git a/packages/ep_products/Classes/Service/IcalService.php b/packages/ep_products/Classes/Service/IcalService.php new file mode 100644 index 0000000..324f195 --- /dev/null +++ b/packages/ep_products/Classes/Service/IcalService.php @@ -0,0 +1,159 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\BpnConnect\ApiClient; +use EP\EpProducts\Domain\Model\Hotel; +use Kigkonsult\Icalcreator\Vcalendar; +use TYPO3\CMS\Core\SingletonInterface; + +class IcalService implements SingletonInterface +{ + private const SUMMARY_UNAVAILABLE = 'Unavailable'; + + /** + * @var ApiClient + */ + protected $apiClient; + + /** + * @var Vcalendar + */ + protected $vcalendar; + + /** + * @param ApiClient $apiClient + */ + public function __construct(ApiClient $apiClient) + { + $this->apiClient = $apiClient; + } + + /** + * @param Hotel $hotel + * @return string + */ + public function createContingentsFeed(Hotel $hotel): string + { + $calendarRows = []; + $hotelCode = trim((string) $hotel->getCode()); + + if ($hotelCode !== '') { + $dateFrom = new \DateTimeImmutable('today'); + + try { + $calendar = $this->apiClient->getCalendar( + $hotelCode, + $dateFrom, + null, + ApiClient::CALENDAR_MODE_RANGES + ); + $calendarRows = isset($calendar['data']) && is_array($calendar['data']) ? $calendar['data'] : []; + } catch (\Throwable $e) { + $calendarRows = []; + } + } + + $this->createCalendarInstance(); + $this->generateUnavailableItems($calendarRows); + + try { + return $this->vcalendar->vtimezonePopulate()->createCalendar(); + } catch (\Exception $e) { + return ''; + } + } + + /** + * @param array $calendarRows + */ + protected function generateUnavailableItems(array $calendarRows): void + { + usort($calendarRows, function (array $left, array $right): int { + return strcmp((string) ($left['date_from'] ?? ''), (string) ($right['date_from'] ?? '')); + }); + + foreach ($calendarRows as $row) { + if (!isset($row['date_from'], $row['date_to'])) { + continue; + } + + if (!$this->isUnavailableStatus($row['status'] ?? 'OK')) { + continue; + } + + $dateFrom = new \DateTimeImmutable($row['date_from']); + $dateTo = new \DateTimeImmutable($row['date_to']); + + if ($dateTo < $dateFrom) { + continue; + } + + $this->addBlockedItem([ + 'from' => $dateFrom, + 'to' => $dateTo->add(new \DateInterval('P1D')), + ]); + } + } + + /** + * @param array $item + */ + protected function addBlockedItem(array $item): void + { + try { + $this->vcalendar + ->newVevent() + ->setDtstart($item['from']) + ->setDtend($item['to']) + ->setSummary(self::SUMMARY_UNAVAILABLE) + ; + } catch (\Exception $e) { + } + } + + /** + * @param mixed $status + * @return bool + */ + protected function isUnavailableStatus($status): bool + { + $status = strtoupper(trim((string) $status)); + + return in_array($status, [ 'BLOCKED', 'ON_REQUEST', 'ONREQUEST' ], true); + } + + protected function createCalendarInstance(): void + { + $this->vcalendar = Vcalendar::factory([ Vcalendar::UNIQUE_ID => 'www.ep-reisen.de' ]); + $this->vcalendar->setXprop( + Vcalendar::X_WR_TIMEZONE, + 'Europe/Berlin' + ); + } +} diff --git a/packages/ep_products/Classes/Service/ImportTimestampService.php b/packages/ep_products/Classes/Service/ImportTimestampService.php new file mode 100644 index 0000000..c3bf175 --- /dev/null +++ b/packages/ep_products/Classes/Service/ImportTimestampService.php @@ -0,0 +1,61 @@ +format('d.m.Y H:i:s')); + } + + public function isImportRequired(string $uploadTimestampFile, string $importTimestampFile): bool + { + $updatedTimestampPath = GeneralUtility::getFileAbsFileName($uploadTimestampFile); + + if (!$updatedTimestampPath || !file_exists($updatedTimestampPath)) { + $this->writeImportTimestamp($importTimestampFile); + + return true; + } + + $line = trim(file($updatedTimestampPath)[0]); + + if (empty($line)) { + return true; + } + + $lastUploadAt = \DateTime::createFromFormat('d.m.Y H:i:s', $line); + + $lastImportAt = $this->readImportTimestamp($importTimestampFile); + + if (null === $lastImportAt || $lastUploadAt > $lastImportAt) { + $this->writeImportTimestamp($importTimestampFile); + + return true; + } + + return false; + } + +} diff --git a/packages/ep_products/Classes/Service/LabelService.php b/packages/ep_products/Classes/Service/LabelService.php new file mode 100644 index 0000000..0d52d3a --- /dev/null +++ b/packages/ep_products/Classes/Service/LabelService.php @@ -0,0 +1,106 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Backend\Utility\BackendUtility; + +class LabelService +{ + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getProductLabel(&$parameters, $parentObject) + { + $product = BackendUtility::getRecord('tx_epproducts_domain_model_product', $parameters['row']['uid']); + $parameters['title'] = $product['name_internal']; + if (!empty($product['code'])) { + $parameters['title'] .= ' (' . $product['code'] . ')'; + } + } + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getHotelLabel(&$parameters, $parentObject) + { + $hotel = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $parameters['row']['uid']); + $parameters['title'] = $hotel['name']; + if (!empty($hotel['code'])) { + $parameters['title'] .= ' (' . $hotel['code'] . ')'; + } + } + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getDateLabel(&$parameters, $parentObject) + { + $date = BackendUtility::getRecord('tx_epproducts_domain_model_date', $parameters['row']['uid']); +// $dateFrom = new \DateTime($date['date_start']); +// $dateTo = new \DateTime($date['date_end']); +// $parameters['title'] = sprintf('%s %s (%s)', $date['product_name'], $date['code'], $dateFrom->format('d.m.Y') . ' - ' . $dateTo->format('d.m.Y')); + $parameters['title'] = sprintf('%s (%s)', $date['product_name'], $date['code']); + } + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getOfficetipLabel(&$parameters, $parentObject) + { + $officetip = BackendUtility::getRecord('tx_epproducts_domain_model_officetip', $parameters['row']['uid']); + $author = BackendUtility::getRecord('tx_epproducts_domain_model_teammember', $officetip['author']); + $parameters['title'] = sprintf('%s (%s)', substr($officetip['label'], 0, 30), $author['name']); + } + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getContingentLabel(&$parameters, $parentObject) + { + $contingent = BackendUtility::getRecord('tx_epproducts_domain_model_contingent', $parameters['row']['uid']); + $hotel = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $contingent['hotel']); + $date = new \DateTime($contingent['date']); + $parameters['title'] = sprintf('%s: %s (%d/%d)', $date->format('d.m.Y'), $hotel['name'], $contingent['available'], $contingent['pax']); + } + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getGroupsPriceConfigLabel(&$parameters, $parentObject) + { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_groupspriceconfig', $parameters['row']['uid']); + $parameters['title'] = sprintf('%s - %s', date('d.m.Y', $record['date_from']), date('d.m.Y', $record['date_to'])); + } +} diff --git a/packages/ep_products/Classes/Service/LocalImportSource.php b/packages/ep_products/Classes/Service/LocalImportSource.php new file mode 100644 index 0000000..f37ffd0 --- /dev/null +++ b/packages/ep_products/Classes/Service/LocalImportSource.php @@ -0,0 +1,56 @@ +isNewImportAvailable()) { + return null; + } + + $path = GeneralUtility::getFileAbsFileName(self::XML_DIR); + $reader = new XmlProductReader( + $path, + GeneralUtility::makeInstance(LogManager::class)->getLogger(XmlProductReader::class) + ); + + return new ProductImport($reader, $reader->getSourceGeneratedAt(), $path); + } + + public function markImported(ProductImport $import): void + { + (new ImportTimestampService())->writeImportTimestamp(self::IMPORT_MARKER); + } + + public function isNewImportAvailable(): bool + { + $timestampService = new ImportTimestampService(); + + $uploadMarkerPath = GeneralUtility::getFileAbsFileName(self::UPLOAD_MARKER); + if ($uploadMarkerPath === '' || !is_file($uploadMarkerPath)) { + // No supplier marker at all: import whatever is in the folder (legacy behaviour). + return true; + } + + $uploadedAt = $timestampService->readImportTimestamp(self::UPLOAD_MARKER); + $lastImportedAt = $timestampService->readImportTimestamp(self::IMPORT_MARKER); + + return $uploadedAt === null || $lastImportedAt === null || $uploadedAt > $lastImportedAt; + } +} diff --git a/packages/ep_products/Classes/Service/LogService.php b/packages/ep_products/Classes/Service/LogService.php new file mode 100644 index 0000000..4e588ce --- /dev/null +++ b/packages/ep_products/Classes/Service/LogService.php @@ -0,0 +1,204 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Dto\FilterOptions; +use EP\EpProducts\Traits\DbConnectionTrait; +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\SingletonInterface; + +class LogService implements SingletonInterface +{ + use DbConnectionTrait; + + /** + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function dumpSearchLogEpreisen() + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('logs.data as data') + ->addSelectLiteral('DATE_FORMAT(FROM_UNIXTIME(logs.time_micro), "%d.%m.%Y, %H:%i Uhr") as date') + ->from('tx_epproducts_log', 'logs') + ->orderBy('time_micro') + ->execute() + ->fetchAll() + ; + + $csv = [ + [ + 'Datum/Uhrzeit', + 'Destinationstyp', + 'Destinationsname', + 'Konzept', + 'Preisbereich', + 'Personenzahl', + 'Startdatum', + 'Enddatum', + 'Seite', + 'Seiten ID', + ], + ]; + + $labels = $this->fetchEntityLabels(); + + foreach ($result as $row) + { + $data = json_decode(substr($row['data'], 2)); + $searchParams = $data->searchParams; + $priceRange = null; + if ($searchParams->priceRange) { + list ($priceFrom, $priceTo) = FilterOptions::$priceRangeOptions[$searchParams->priceRange]; + $priceRange = sprintf('%s - %s', $priceFrom, $priceTo); + } + $pageTitle = ''; + if ($searchParams->pageUid) { + $page = BackendUtility::getRecord('pages', $searchParams->pageUid); + $pageTitle = $page['title']; + } + $csv[] = [ + $row['date'], + $searchParams->destinationType, + $labels[$searchParams->destinationType][$searchParams->destinationUid], + $labels['concept'][$searchParams->conceptUid], + $priceRange, + $searchParams->pax, + $searchParams->dateFrom, + $searchParams->dateTo, + $pageTitle, + $searchParams->pageUid, + ]; + } + + return $csv; + } + + /** + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function dumpSearchLogEpevents() + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('logs.data as data') + ->addSelectLiteral('DATE_FORMAT(FROM_UNIXTIME(logs.time_micro), "%d.%m.%Y, %H:%i Uhr") as date') + ->from('tx_epevents_log', 'logs') + ->orderBy('time_micro') + ->execute() + ->fetchAll() + ; + + $csv = [ + [ + 'Datum/Uhrzeit', + 'Suchbegriff', + 'Seite', + 'Seiten ID', + ], + ]; + + foreach ($result as $row) + { + $data = json_decode(substr($row['data'], 2)); + $pageTitle = ''; + if ($data->pageUid) { + $page = BackendUtility::getRecord('pages', $data->pageUid); + $pageTitle = $page['title']; + } + $csv[] = [ + $row['date'], + $data->search, + $pageTitle, + $data->pageUid, + ]; + } + + return $csv; + } + + /** + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + private function fetchEntityLabels() + { + $labels = [ + 'country' => [], + 'region' => [], + ]; + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('uid', 'name') + ->from('tx_epproducts_domain_model_country', 'c') + ->execute() + ->fetchAll() + ; + + foreach ($result as $row) + { + $labels['country'][$row['uid']] = $row['name']; + } + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('uid', 'name') + ->from('tx_epproducts_domain_model_region') + ->execute() + ->fetchAll() + ; + + foreach ($result as $row) + { + $labels['region'][$row['uid']] = $row['name']; + } + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('uid', 'name') + ->from('tx_epproducts_domain_model_concept') + ->execute() + ->fetchAll() + ; + + foreach ($result as $row) + { + $labels['concept'][$row['uid']] = $row['name']; + } + + return $labels; + } + +} diff --git a/packages/ep_products/Classes/Service/MyEpImageService.php b/packages/ep_products/Classes/Service/MyEpImageService.php new file mode 100644 index 0000000..59233d8 --- /dev/null +++ b/packages/ep_products/Classes/Service/MyEpImageService.php @@ -0,0 +1,63 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Resource\FileRepository; +use TYPO3\CMS\Extbase\Service\ImageService; + +class MyEpImageService extends AbstractImageService +{ + /** + * @var string + */ + protected $field = 'images'; + + /** + * @var array + */ + protected $imageSizes = [ + 's' => [ + 'width' => '480', + 'height' => '360c-100', + ], + 'm' => [ + 'width' => '768', + 'height' => '576c-100', + ], + 'l' => [ + 'width' => '1280', + 'height' => '960c-100', + ] + ]; + + public function __construct(FileRepository $fileRepository, ImageService $imageService, FrontendInterface $cache, string $table = 'tx_epproducts_domain_model_hotel') + { + parent::__construct($fileRepository, $imageService, $cache); + $this->table = $table; + } +} diff --git a/packages/ep_products/Classes/Service/MyEpUrlResolverService.php b/packages/ep_products/Classes/Service/MyEpUrlResolverService.php new file mode 100644 index 0000000..5b4dc30 --- /dev/null +++ b/packages/ep_products/Classes/Service/MyEpUrlResolverService.php @@ -0,0 +1,55 @@ +getTypoScriptSettings(); + $productionUrl = $this->resolveUrlSetting( + $settings, + 'myEpUrl', + (string) $config->get('ep_products', 'myEpUrl') + ); + $stagingUrl = $this->resolveUrlSetting( + $settings, + 'myEpStagingUrl', + (string) $config->get('ep_products', 'myEpStagingUrl') + ); + + if ('' !== $stagingUrl && !empty($_COOKIE['ep_myep_staging'])) { + return $stagingUrl; + } + + return $productionUrl; + } + + private function getTypoScriptSettings(): array + { + $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); + $settings = GeneralUtility::removeDotsFromTS( + $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + + return $settings['plugin']['tx_epproducts']['settings'] ?? []; + } + + private function resolveUrlSetting(array $settings, string $key, string $fallback): string + { + $value = trim((string) ($settings[$key] ?? '')); + + return $value !== '' ? $value : $fallback; + } +} diff --git a/packages/ep_products/Classes/Service/PickupOverviewService.php b/packages/ep_products/Classes/Service/PickupOverviewService.php new file mode 100644 index 0000000..f05d007 --- /dev/null +++ b/packages/ep_products/Classes/Service/PickupOverviewService.php @@ -0,0 +1,82 @@ +dateRepository = $dateRepository; + } + + /** + * @return array{pickups: array} + */ + public function getOverview(Product $product, ?Hotel $hotel): array + { + $overview = ['pickups' => []]; + if ($hotel === null || $product->isDaytrip()) { + return $overview; + } + + $dates = $this->dateRepository->findPickupsForProductAndHotel((int) $product->getUid(), (int) $hotel->getUid()); + foreach ($dates as $serializedPickups) { + $pickups = $this->readPickups((string) $serializedPickups); + if ($pickups !== []) { + $overview['pickups'] = $pickups; + break; + } + } + + return $overview; + } + + /** + * Keeps the order of the import, which is the boarding order. + */ + private function readPickups(string $serializedPickups): array + { + $datePickups = @unserialize($serializedPickups, ['allowed_classes' => false]); + if (!is_array($datePickups)) { + return []; + } + + $pickups = []; + foreach ($datePickups as $pickup) { + $busProId = (int) ($pickup['bus_pro_id'] ?? 0); + if ($busProId === 0 || isset($pickups[$busProId])) { + continue; + } + $pickups[$busProId] = [ + 'busProId' => $busProId, + 'city' => (string) ($pickup['city'] ?? ''), + 'street' => (string) ($pickup['street'] ?? ''), + // A pickup's price is one leg; the surcharge is split evenly between out- and inbound. + 'price' => 2 * (float) ($pickup['price'] ?? 0.0), + 'time' => (string) ($pickup['time'] ?? ''), + ]; + } + + return array_values($pickups); + } +} diff --git a/packages/ep_products/Classes/Service/ProductDataService.php b/packages/ep_products/Classes/Service/ProductDataService.php new file mode 100644 index 0000000..f1d65b2 --- /dev/null +++ b/packages/ep_products/Classes/Service/ProductDataService.php @@ -0,0 +1,233 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Repository\HotelRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use TYPO3\CMS\Core\SingletonInterface; + +class ProductDataService implements SingletonInterface +{ + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @var HotelRepository + */ + protected $hotelRepository; + + /** + * @var ProductImageService + */ + protected $productImageService; + + /** + * @var HotelImageService + */ + protected $hotelImageService; + + /** + * @param ProductRepository $productRepository + * @param HotelRepository $hotelRepository + * @param HotelImageService $hotelImageService + * @param ProductImageService $productImageService + */ + public function __construct( + ProductRepository $productRepository, + HotelRepository $hotelRepository, + HotelImageService $hotelImageService, + ProductImageService $productImageService + ) { + $this->productRepository = $productRepository; + $this->hotelRepository = $hotelRepository; + $this->productImageService = $productImageService; + $this->hotelImageService = $hotelImageService; + } + + /** + * @param array $filterSettings + * + * @return array + */ + public function getTeaser(array $filterSettings): ?array + { + $teaserData = $this->productRepository->getTeasers($filterSettings); + $teasers = $this->preprocessTeasergroup($teaserData); + + if (0 === count($teasers)) { + return null; + } + + return reset($teasers); + } + + /** + * @param array $filterSettings + * @param array $excludedConceptUids + * @param int $limit + * + * @return array + */ + public function getTeasergroup(array $filterSettings, array $excludedConceptUids = [], int $limit = 0): array + { + $teaserData = $this->productRepository->getTeasers($filterSettings, $excludedConceptUids); + $teasers = $this->preprocessTeasergroup($teaserData); + + if ($limit > 0) { + return \array_slice($teasers, 0, $limit); + } + + return $teasers; + } + + /** + * @param array $teaserData + * + * @return array + */ + public function preprocessTeasergroup(array $teaserData): array + { + $data = []; + + foreach ($teaserData as $row) { + if (!array_key_exists($row['productUid'], $data)) { + $data[$row['productUid']] = $this->createTeaserData($row); + } + + $teaser =& $data[$row['productUid']]; + + if (!\in_array($row['nights'], $teaser['nights'], false)) { + $teaser['nights'][] = $row['nights']; + } + + if (!\in_array($row['dateStart'], $teaser['dates'], false)) { + $teaser['dates'][] = $row['dateStart']; + } + + if (!$teaser['skiPassIncluded'] && $row['skiPassIncluded']) { + $teaser['skiPassIncluded'] = true; + } + + if (!$teaser['busIncluded'] && $row['busIncluded']) { + $teaser['busIncluded'] = true; + } + + if ($teaser['minDateStart'] === null || $teaser['minDateStart'] > $row['dateStart']) { + $teaser['minDateStart'] = $row['dateStart']; + } + + if ($teaser['maxDateEnd'] === null || $teaser['maxDateEnd'] < $row['dateEnd']) { + $teaser['maxDateEnd'] = $row['dateEnd']; + } + + if ($teaser['minPrice'] === null || $teaser['minPrice'] > $row['minPrice']) { + $teaser['minPrice'] = $row['minPrice']; + } + + if ($teaser['hotel']['uid'] !== $row['hotelUid']) { + $teaser['hotelCount']++; + } + + if (!array_key_exists($row['hotelCategory'], $teaser['hotelCategories'])) { + $teaser['hotelCategories'][$row['hotelCategory']] = Hotel::$categoryLabels[$row['hotelCategory']]; + ksort($teaser['hotelCategories']); + } + } + + return $data; + } + + /** + * @param array $teaserData + * + * @return array + */ + public function createTeaserData(array $teaserData): array + { + $hotelCategory = Hotel::$categoryLabels[$teaserData['hotelCategory']] ?? null; + + // Use compound key, hotel is not unique + $key = $teaserData['hotelUid'] . ':' . $teaserData['productUid']; + + return [ + 'key' => $key, + 'minDateStart' => null, + 'maxDateEnd' => null, + 'minPrice' => null, + 'hotelCount' => 1, + 'pseudoPrice' => $teaserData['pseudoPrice'], + 'board' => $teaserData['board'], + 'earlybird' => $teaserData['earlybird'], + 'new' => $teaserData['new'], + 'daytrip' => $teaserData['daytrip'], + 'hideBookingButton' => $teaserData['hideBookingButton'], + 'lowContingent' => $teaserData['lowContingent'], + 'servicesIncluded' => unserialize($teaserData['servicesIncluded'], ['allowed_classes' => false]), + 'season' => $teaserData['season'], + 'product' => [ + 'uid' => $teaserData['productUid'], + 'name' => $teaserData['productName'], + 'subline' => $teaserData['productSubline'], + 'detailPage' => $teaserData['productDetailPage'], + 'slug' => $teaserData['productSlug'], + 'fact' => $teaserData['productFact'], + 'feature' => $teaserData['productFeature'], + 'teaser' => $teaserData['productTeaser'], + 'images' => $this->productImageService->getImages($teaserData['productUid']), + ], + 'concept' => [ + 'uid' => $teaserData['conceptUid'], + 'name' => $teaserData['conceptName'], + 'code' => $teaserData['conceptCode'], + 'popupText' => $teaserData['conceptPopupText'], + ], + 'hotel' => [ + 'name' => $teaserData['hotelName'], + 'category' => $hotelCategory, + 'fact' => $teaserData['hotelFact'], + 'uid' => $teaserData['hotelUid'], + 'images' => $this->hotelImageService->getImages($teaserData['hotelUid']), + ], + 'country' => [ + 'name' => $teaserData['countryName'], + 'code' => $teaserData['countryCode'], + ], + 'region' => [ + 'name' => $teaserData['regionName'], + 'fact' => $teaserData['regionFact'], + 'feature' => $teaserData['regionFeature'], + ], + 'dates' => [], + 'nights' => [], + 'hotelCategories' => [], + ]; + } + +} diff --git a/packages/ep_products/Classes/Service/ProductImageService.php b/packages/ep_products/Classes/Service/ProductImageService.php new file mode 100644 index 0000000..f23da27 --- /dev/null +++ b/packages/ep_products/Classes/Service/ProductImageService.php @@ -0,0 +1,66 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class ProductImageService extends AbstractImageService +{ + /** + * @var string + */ + protected $table = 'tx_epproducts_domain_model_product'; + + /** + * @var string + */ + protected $field = 'teaser_images'; + + /** + * @var array + */ + protected $imageSizes = [ + 'xs' => [ + 'width' => '320', + 'height' => '160c-100', + ], + 's' => [ + 'width' => '350', + 'height' => '175c-100', + ], + 'm' => [ + 'width' => '360', + 'height' => '200c-100', + ], + 'l' => [ + 'width' => '370', + 'height' => '220c-100', + ], + 'xl' => [ + 'width' => '500', + 'height' => '250c-100', + ], + ]; +} diff --git a/packages/ep_products/Classes/Service/ProductImport.php b/packages/ep_products/Classes/Service/ProductImport.php new file mode 100644 index 0000000..2cdc0d4 --- /dev/null +++ b/packages/ep_products/Classes/Service/ProductImport.php @@ -0,0 +1,56 @@ + + */ + private $rows; + + /** + * @var \DateTimeImmutable|null + */ + private $sourceGeneratedAt; + + /** + * @var string|null + */ + private $location; + + /** + * @param iterable $rows + * @param \DateTimeImmutable|null $sourceGeneratedAt + * @param string|null $location local directory of an XML export, null otherwise + */ + public function __construct(iterable $rows, ?\DateTimeImmutable $sourceGeneratedAt, ?string $location = null) + { + $this->rows = $rows; + $this->sourceGeneratedAt = $sourceGeneratedAt; + $this->location = $location; + } + + /** + * @return iterable + */ + public function getRows(): iterable + { + return $this->rows; + } + + public function getSourceGeneratedAt(): ?\DateTimeImmutable + { + return $this->sourceGeneratedAt; + } + + public function getLocation(): ?string + { + return $this->location; + } +} diff --git a/packages/ep_products/Classes/Service/ProductImportSource.php b/packages/ep_products/Classes/Service/ProductImportSource.php new file mode 100644 index 0000000..e477a75 --- /dev/null +++ b/packages/ep_products/Classes/Service/ProductImportSource.php @@ -0,0 +1,31 @@ +getMode(); + if ($mode === self::MODE_LOCAL) { + return GeneralUtility::makeInstance(LocalImportSource::class); + } + if ($mode === self::MODE_API) { + return GeneralUtility::makeInstance(ApiImportSource::class); + } + + return GeneralUtility::makeInstance(SftpImportSource::class); + } + + public function getMode(): string + { + $config = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('ep_products'); + $mode = strtolower(trim((string)(is_array($config) ? ($config['productImportSource'] ?? '') : ''))); + + return in_array($mode, [self::MODE_LOCAL, self::MODE_API], true) ? $mode : self::MODE_SFTP; + } +} diff --git a/packages/ep_products/Classes/Service/ResellerDataService.php b/packages/ep_products/Classes/Service/ResellerDataService.php new file mode 100644 index 0000000..5340a2f --- /dev/null +++ b/packages/ep_products/Classes/Service/ResellerDataService.php @@ -0,0 +1,187 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Model\Reseller; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Domain\Serialization\Date; +use EP\EpProducts\Domain\Serialization\Hotel as SerializedHotel; +use EP\EpProducts\Domain\Serialization\Product as SerializedProduct; +use EP\EpProducts\Domain\Serialization\Room; +use EP\EpProducts\Traits\DbConnectionTrait; +use EP\EpProducts\Utility\BookingUrlUtility; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class ResellerDataService implements SingletonInterface +{ + use DbConnectionTrait; + + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @param ProductRepository $productRepository + */ + public function __construct(ProductRepository $productRepository) + { + $this->productRepository = $productRepository; + } + + /** + * @param Reseller $reseller + * @param string $myEpUrl + * @param array|null $imageProcessingInstructions + * @param string $type + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function preprocessAll(Reseller $reseller, string $myEpUrl, array $imageProcessingInstructions = null, $type = 'facebook') + { + $data = []; + $products = $reseller->getProducts(); + + foreach ($products as $product) { + /** @var Product $product */ + $data['product'][] = $this->preprocessSingle( + $product, + $reseller, + $myEpUrl, + $imageProcessingInstructions, + $type + ); + } + + return $data; + } + + /** + * @param Product $product + * @param Reseller $reseller + * @param string $myEpUrl + * @param array|null $imageProcessingInstructions + * @param string $type + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function preprocessSingle( + Product $product, + Reseller $reseller, + string $myEpUrl, + array $imageProcessingInstructions = null, + $type = 'facebook' + ) { + $queryData = 'google' !== $type ? [] : [ + 'utm_source' => 'google', + 'utm_medium' => 'cpc', + 'utm_campaign' => 'dynamisches-remarketing', + 'utm_content' => $product->getCode(), + ]; + + $data = SerializedProduct::fromEntity( + $product, + $imageProcessingInstructions, + $queryData + ); + + $hotels = $product->getHotels(); + $paCode = $reseller->getPaCode(); + + foreach ($hotels as $hotel) { + /** @var Hotel $hotel */ + $hotelData = SerializedHotel::fromEntity($hotel); + $data['hotels']['hotel'][] = $hotelData; + if (false === $product->getBookable()) { + $hotelData['dates']['date'] = []; + } else { + $dates = $this->productRepository->getDatesForExport($product, $hotel); + $hotelData['dates']['date'] = $this->preprocessDates($dates, $myEpUrl, $paCode); + } + } + + return $data; + } + + /** + * @param array $dates + * @param string $myEpUrl + * @param string|null $paCode + * @return array + */ + public function preprocessDates(array $dates, string $myEpUrl, $paCode = null) + { + $data = []; + + foreach ($dates as $row) { + if (!isset($data[$row['dateStart']])) { + $date = Date::fromArray($row); + $data[$row['dateStart']] = $date; + } + + $data[$row['dateStart']]['bookingurl'] = BookingUrlUtility::generateUrl([ + 'dateId' => $row['dateBusProId'], + 'hotelId' => $row['hotelBusProId'], + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + ]); + + $data[$row['dateStart']]['rooms']['room'][] = Room::fromArray($row); + } + + return array_values($data); + } + + /** + * @return bool|array + * @throws \Doctrine\DBAL\DBALException + */ + public function getResellerForCurrentDomain() + { + $currentDomain = GeneralUtility::getIndpEnv('HTTP_HOST'); + + $qb = $this->getDbConnection()->createQueryBuilder(); + + return $qb + ->select('r.name as name', 'r.pa_code as paCode') + ->from('tx_epproducts_domain_model_reseller', 'r') + ->innerJoin('r', 'sys_domain', 'd', 'r.uid = d.tx_epproducts_reseller') + ->where('d.domainName = :domain') + ->andWhere('r.deleted = 0') + ->andWhere('r.hidden = 0') + ->andWhere('d.hidden = 0') + ->setParameter('domain', $currentDomain) + ->execute() + ->fetch() + ; + } + +} diff --git a/packages/ep_products/Classes/Service/RoomTypeResolver.php b/packages/ep_products/Classes/Service/RoomTypeResolver.php new file mode 100644 index 0000000..cefcec8 --- /dev/null +++ b/packages/ep_products/Classes/Service/RoomTypeResolver.php @@ -0,0 +1,129 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Room; + +/** + * Derives the room type from the data each element carries. + * + * Replaces the former tx_epproducts_domain_model_roommapping lookup: zimmercode is + * neither unique per room nor a function of room type, so a code-keyed table cannot + * classify this data. MaxPax is the advertised occupancy and is present on every row. + */ +class RoomTypeResolver +{ + /** + * Phrases in zimmertext that mark a bed shared with other customers. + * + * @var array + */ + const LABEL_PATTERNS_WITH_OTHERS = [ + 'bett im', + 'mit ander', + ]; + + /** + * Phrases in zimmertext that mark a deliberate single use of a larger room. + * + * @var array + */ + const LABEL_PATTERNS_SINGLE_USE = [ + 'einzel', + '1 person', + ]; + + /** + * @param string $code zimmercode + * @param string $label zimmertext + * @param int $maxPax MaxPax + * @param int $minPax MinPax + * @return int one of the Room::ROOM_TYPE_* constants + */ + public function resolve(string $code, string $label, int $maxPax, int $minPax): int + { + // Not a room: "Babypreis für 0-2 Jährige". Kept in the date record for its + // price and availability, but hidden from the filter. + if ($code === DateImportService::CODE_BABYROOM) { + return Room::ROOM_TYPE_UNCLASSIFIED; + } + + if ($this->matchesAny($label, static::LABEL_PATTERNS_WITH_OTHERS)) { + return Room::ROOM_TYPE_WITH_OTHERS; + } + + $pax = $maxPax > 0 ? $maxPax : $minPax; + + if ($pax >= 1) { + // Type numbering is chosen so that type === pax for pax 1-8. + return $pax >= 9 ? Room::ROOM_TYPE_FOR_NINE_PLUS : $pax; + } + + // No usable occupancy - should never happen on current data, the caller logs it. + return Room::ROOM_TYPE_WITH_OTHERS; + } + + /** + * Tripwire for an under-matching label pattern: a row classified as a single room + * whose label carries no single-use marker is most likely a shared bed phrased in + * a way LABEL_PATTERNS_WITH_OTHERS does not yet cover. + * + * @param string $code zimmercode + * @param string $label zimmertext + * @param int $maxPax MaxPax + * @return bool + */ + public function needsReview(string $code, string $label, int $maxPax): bool + { + if ($this->resolve($code, $label, $maxPax, $maxPax) !== Room::ROOM_TYPE_FOR_ONE) { + return false; + } + + if ($maxPax !== 1) { + return false; + } + + return !$this->matchesAny($label, static::LABEL_PATTERNS_SINGLE_USE); + } + + /** + * @param string $label + * @param array $patterns + * @return bool + */ + protected function matchesAny(string $label, array $patterns): bool + { + foreach ($patterns as $pattern) { + if (mb_strpos(mb_strtolower($label), mb_strtolower($pattern)) !== false) { + return true; + } + } + + return false; + } +} diff --git a/packages/ep_products/Classes/Service/SearchResultService.php b/packages/ep_products/Classes/Service/SearchResultService.php new file mode 100644 index 0000000..768097c --- /dev/null +++ b/packages/ep_products/Classes/Service/SearchResultService.php @@ -0,0 +1,287 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\SearchResult; +use EP\EpProducts\Domain\Repository\ProductRepository; +use Symfony\Component\OptionsResolver\OptionsResolver; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class SearchResultService implements SingletonInterface +{ + public const DESTINATION_TYPE_COUNTRY = 'country'; + public const DESTINATION_TYPE_REGION = 'region'; + public const DESTINATION_TYPE_CITY = 'city'; + public const DESTINATION_TYPE_CONCEPT = 'concept'; + + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @var HotelImageService + */ + protected $hotelImageService; + + /** + * @var ProductImageService + */ + protected $productImageService; + + /** + * @param ProductRepository $productRepository + * @param HotelImageService $hotelImageService + * @param ProductImageService $productImageService + */ + public function __construct( + ProductRepository $productRepository, + HotelImageService $hotelImageService, + ProductImageService $productImageService + ) + { + $this->productRepository = $productRepository; + $this->hotelImageService = $hotelImageService; + $this->productImageService = $productImageService; + } + + public function getNormalizedSearchParams(array $searchParams = []): array + { + $optionsResolver = new OptionsResolver(); + $optionsResolver->setDefaults([ + 'priceRange' => null, + 'dateFrom' => null, + 'dateTo' => null, + 'destinationUid' => null, + 'destinationType' => null, + 'nights' => null, + 'pax' => 0, + 'pageUid' => null, + ]); + + return $optionsResolver->resolve($searchParams); + } + + public function getResult(array $filterSettings, array $excludedConceptUids = []): SearchResult + { + $searchResultData = $this->productRepository->getSearchResult($filterSettings, $excludedConceptUids); + + return $this->preprocess($searchResultData, true); + } + + public function preprocess(array $searchResultData, bool $aggregateEvents = false): SearchResult + { + $data = []; + $total = 0; + + foreach ($searchResultData as $row) { + // Concepts + if (!array_key_exists($row['conceptUid'], $data)) { + $data[$row['conceptUid']] = [ + 'concept' => [ + 'uid' => $row['conceptUid'], + 'name' => $row['conceptName'], + 'code' => $row['conceptCode'], + 'sorting' => $row['conceptSorting'], + 'popupText' => $row['conceptPopupText'], + ], + 'items' => [], + ]; + } + + $items =& $data[$row['conceptUid']]['items']; + + $isAggregatedEvent = $aggregateEvents && 'evt' === $row['conceptCode']; + + // Events are product-level search results. Other results (including + // watchlist items) remain tied to a particular hotel. + $key = $isAggregatedEvent + ? (string) $row['productUid'] + : $row['hotelUid'] . ':' . $row['productUid']; + + $hotelCategory = Hotel::$categoryLabels[$row['hotelCategory']] ?? null; + + if (!array_key_exists($key, $items)) { + $items[$key] = [ + 'key' => $key, + 'minDateStart' => null, + 'maxDateEnd' => null, + 'minPrice' => null, + 'detailPageUri' => null, + 'earlybird' => $row['earlybird'], + 'daytrip' => $row['daytrip'], + 'season' => $row['season'], + 'hideBookingButton' => $row['hideBookingButton'], + 'busAvailable' => $row['busAvailable'], + 'available' => $row['available'], + 'lowContingent' => $row['lowContingent'], + 'servicesIncluded' => unserialize($row['servicesIncluded'], ['allowed_classes' => false]), + 'hotel' => [ + 'uid' => $row['hotelUid'], + 'name' => $row['hotelName'], + 'teaser' => $row['hotelTeaser'], + 'images' => $this->hotelImageService->getImages($row['hotelUid']), + 'fact' => $row['hotelFact'], + 'category' => $hotelCategory, + 'type' => $row['hotelType'], + ], + 'country' => [ + 'uid' => $row['countryUid'], + 'code' => $row['countryCode'], + 'name' => $row['countryName'], + ], + 'region' => [ + 'uid' => $row['regionUid'], + 'name' => $row['regionName'], + 'fact' => $row['regionFact'], + 'feature' => $row['regionFeature'], + ], + 'product' => [ + 'uid' => $row['productUid'], + 'detailPage' => $row['productDetailPage'], + 'name' => $row['productName'], + 'subline' => $row['productSubline'], + 'feature' => $row['productFeature'], + 'fact' => $row['productFact'], + 'images' => $isAggregatedEvent + ? $this->productImageService->getImages($row['productUid']) + : [], + ], + 'board' => $row['board'], + 'boardUid' => $row['boardUid'], + 'dates' => [], + 'nights' => [], + 'teaserNights' => [], + 'rooms' => [], + 'new' => $row['new'], + 'skiPassIncluded' => $row['skiPassIncluded'], + 'filterMetadata' => [ + 'destinations' => [], + 'hotelTypes' => [], + 'boardTypes' => [], + ], + ]; + $total++; + } + + $item =& $items[$key]; + + $destination = [ + 'country' => [ + 'uid' => $row['countryUid'], + 'code' => $row['countryCode'], + 'name' => $row['countryName'], + ], + 'region' => [ + 'uid' => $row['regionUid'], + 'name' => $row['regionName'], + 'fact' => $row['regionFact'], + 'feature' => $row['regionFeature'], + ], + ]; + if (!\in_array($destination, $item['filterMetadata']['destinations'], false)) { + $item['filterMetadata']['destinations'][] = $destination; + } + $hotelType = $row['hotelType'] ?? $item['hotel']['type']; + if (!\in_array($hotelType, $item['filterMetadata']['hotelTypes'], false)) { + $item['filterMetadata']['hotelTypes'][] = $hotelType; + } + $boardType = ['uid' => $row['boardUid'] ?? null, 'label' => $row['board']]; + if (!\in_array($boardType, $item['filterMetadata']['boardTypes'], false)) { + $item['filterMetadata']['boardTypes'][] = $boardType; + } + + foreach (GeneralUtility::trimExplode(',', (string) ($row['roomTypes'] ?? ''), true) as $roomType) { + if (!\in_array($roomType, $item['rooms'], false)) { + $item['rooms'][] = $roomType; + } + } + + if (!\in_array($row['nights'], $item['nights'], false)) { + $item['nights'][] = $row['nights']; + } + + if (!\in_array($row['dateStart'], $item['dates'], false)) { + $item['dates'][] = $row['dateStart']; + } + + $dateStart = $row['dateStart']; + $dateEnd = $row['dateEnd']; + + if ($item['minDateStart'] === null + || $item['minDateStart'] > $dateStart) { + $item['minDateStart'] = $dateStart; + } + + if ($item['maxDateEnd'] === null + || $item['maxDateEnd'] < $dateEnd) { + $item['maxDateEnd'] = $dateEnd; + } + + if ($item['minPrice'] === null + || $item['minPrice'] > $row['minPrice']) { + $item['minPrice'] = $row['minPrice']; + // Teaser-specific offer data must describe the cheapest row. + $item['teaserNights'] = [$row['nights']]; + $item['board'] = $row['board']; + $item['boardUid'] = $row['boardUid']; + $item['daytrip'] = $row['daytrip']; + $item['season'] = $row['season']; + $item['hideBookingButton'] = $row['hideBookingButton']; + $item['servicesIncluded'] = unserialize($row['servicesIncluded'], ['allowed_classes' => false]); + $item['new'] = $row['new']; + $item['skiPassIncluded'] = $row['skiPassIncluded']; + } + + // Availability indicators describe the consolidated set, not only + // the row that supplies the teaser price. + $item['earlybird'] = (bool) $item['earlybird'] || (bool) $row['earlybird']; + $item['busAvailable'] = (bool) $item['busAvailable'] || (bool) $row['busAvailable']; + $item['lowContingent'] = (bool) $item['lowContingent'] || (bool) $row['lowContingent']; + $item['available'] = (bool) $item['available'] || (bool) $row['available']; + + } + + // Sort products by minprice within concept sections except for 'event' concept + foreach ($data as &$section) { + if ('evt' === $section['concept']['code']) { + continue; + } + + uasort($section['items'], function ($a, $b) { + return strcmp($a['minPrice'], $b['minPrice']); + }); + } + + // Turn result data into simple array to ensure order in js + $result = array_values($data); + + return new SearchResult($result, $total); + } +} diff --git a/packages/ep_products/Classes/Service/SftpImportException.php b/packages/ep_products/Classes/Service/SftpImportException.php new file mode 100644 index 0000000..3b6c51c --- /dev/null +++ b/packages/ep_products/Classes/Service/SftpImportException.php @@ -0,0 +1,7 @@ +fetchIfNewer(); + if ($path === null) { + return null; + } + + $reader = new XmlProductReader($path, $this->logger); + + return new ProductImport($reader, $reader->getSourceGeneratedAt(), $path); + } + + /** + * {@see ProductImportSource::markImported()} — publish the download as the new + * change-detection baseline (only reached after a successful import), then + * refresh the display-only marker for the BE module and system-info toolbar. + */ + public function markImported(ProductImport $import): void + { + $this->commit((string)$import->getLocation()); + (new ImportTimestampService())->writeImportTimestamp(self::IMPORT_MARKER); + } + + /** + * {@see ProductImportSource::isNewImportAvailable()} — alias for {@see isRemoteNewer()}. + */ + public function isNewImportAvailable(): bool + { + return $this->isRemoteNewer(); + } + + /** + * Timestamp of the export currently offered on the remote server, or null when + * the marker is absent/unreadable/unparseable (supplier upload not finished). + */ + public function getRemoteUploadTimestamp(): ?\DateTime + { + $config = $this->buildConfig(); + $sftp = $this->connect($config); + try { + [, $markerBytes] = $this->readRemoteMarker($sftp, $config['remotePath']); + } finally { + $sftp->disconnect(); + } + + return $markerBytes !== null ? $this->parseMarkerTimestamp($markerBytes) : null; + } + + /** + * Timestamp of the export we last successfully imported (staged marker copy). + */ + public function getStagedImportedTimestamp(): ?\DateTime + { + $marker = $this->stagingBasePath() . '/' . self::PUBLISHED_DIR . '/' . self::MARKER_FILE; + if (!is_file($marker)) { + return null; + } + + return $this->parseMarkerTimestamp((string)file_get_contents($marker)); + } + + /** + * True when the remote export is newer than the last imported one (or nothing + * has been imported yet). False when there is nothing to do. + */ + public function isRemoteNewer(): bool + { + $remoteTs = $this->getRemoteUploadTimestamp(); + if ($remoteTs === null) { + return false; + } + + $stagedTs = $this->getStagedImportedTimestamp(); + + return $stagedTs === null || $remoteTs > $stagedTs; + } + + /** + * Freshness check + (only when newer) full download into a fresh temp dir. + * Returns the temp-dir path — NOT yet published. The caller runs + * DateImportService::import() against it and calls {@see commit()} on success. + * Returns null when the remote export is not newer / not ready. + * + * @throws SftpImportException on connect/auth/host-key/partial-transfer failure + */ + public function fetchIfNewer(): ?string + { + $this->cleanupStale(); + $config = $this->buildConfig(); + $sftp = $this->connect($config); + + try { + [$remoteDir, $markerBytes] = $this->readRemoteMarker($sftp, $config['remotePath']); + $remoteTs = $markerBytes !== null ? $this->parseMarkerTimestamp($markerBytes) : null; + + if ($remoteTs === null) { + $this->logWarning('remote upload marker missing or unparseable; skipping product import', [ + 'host' => $config['host'], + 'remotePath' => $config['remotePath'], + ]); + + return null; + } + + $stagedTs = $this->getStagedImportedTimestamp(); + if ($stagedTs !== null && $remoteTs <= $stagedTs) { + return null; + } + + return $this->downloadInto($sftp, $remoteDir, $markerBytes); + } finally { + $sftp->disconnect(); + } + } + + /** + * Unconditional download into a fresh temp dir. Returns the temp-dir path. + * + * @throws SftpImportException + */ + public function fetch(): string + { + $this->cleanupStale(); + $config = $this->buildConfig(); + $sftp = $this->connect($config); + + try { + [$remoteDir, $markerBytes] = $this->readRemoteMarker($sftp, $config['remotePath']); + if ($markerBytes === null) { + throw new SftpImportException( + 'Remote export has no readable ' . self::MARKER_FILE . ' under "' . $config['remotePath'] . '"' + ); + } + + return $this->downloadInto($sftp, $remoteDir, $markerBytes); + } finally { + $sftp->disconnect(); + } + } + + /** + * Publish a temp dir produced by {@see fetchIfNewer()} / {@see fetch()} to + * .../current, making its marker the new change-detection baseline. Call only + * after import() succeeded. + * + * @throws SftpImportException + */ + public function commit(string $stagedPath): string + { + $base = $this->stagingBasePath(); + $stagedPath = rtrim($stagedPath, '/'); + + if (strpos($stagedPath . '/', $base . '/') !== 0 || !is_dir($stagedPath)) { + throw new SftpImportException('Refusing to publish unknown staging directory: ' . $stagedPath); + } + + $published = $base . '/' . self::PUBLISHED_DIR; + + if (is_dir($published)) { + $retired = $published . '.old-' . time() . '-' . bin2hex(random_bytes(3)); + if (!@rename($published, $retired)) { + throw new SftpImportException('Cannot move previous import aside: ' . $published); + } + } + + if (!@rename($stagedPath, $published)) { + throw new SftpImportException('Cannot publish staging directory to ' . $published); + } + + foreach ((array)glob($base . '/' . self::PUBLISHED_DIR . '.old-*', GLOB_ONLYDIR) as $old) { + GeneralUtility::rmdir($old, true); + } + + return $published; + } + + /** + * @param array{host:string,port:int,user:string,password:string,remotePath:string,timeout:int,hostKey:string} $config + */ + private function connect(array $config): SFTP + { + try { + $sftp = new SFTP($config['host'], $config['port'], $config['timeout']); + } catch (\Throwable $e) { + throw new SftpImportException( + sprintf('Cannot connect to SFTP host %s:%d: %s', $config['host'], $config['port'], $e->getMessage()), + 0, + $e + ); + } + + if ($config['hostKey'] !== '') { + $this->verifyHostKey($sftp, $config['host'], $config['hostKey']); + } + + try { + $authenticated = $sftp->login($config['user'], $config['password']); + } catch (\Throwable $e) { + throw new SftpImportException( + sprintf('Cannot connect to SFTP host %s:%d: %s', $config['host'], $config['port'], $e->getMessage()), + 0, + $e + ); + } + + if ($authenticated !== true) { + throw new SftpImportException(sprintf('SFTP authentication failed for user "%s"', $config['user'])); + } + + return $sftp; + } + + private function verifyHostKey(SFTP $sftp, string $host, string $expected): void + { + try { + $actual = $sftp->getServerPublicHostKey(); + } catch (\Throwable $e) { + throw new SftpImportException('Cannot read SSH host key for ' . $host . ': ' . $e->getMessage(), 0, $e); + } + + if ($actual === false) { + throw new SftpImportException('Cannot read SSH host key for ' . $host); + } + + $expected = trim($expected); + $actual = trim($actual); + + if (hash_equals($actual, $expected)) { + return; + } + + $parts = explode(' ', $actual); + $rawKey = isset($parts[1]) ? base64_decode($parts[1], true) : false; + if ($rawKey !== false) { + $fingerprint = 'SHA256:' . rtrim(base64_encode(hash('sha256', $rawKey, true)), '='); + if (hash_equals($fingerprint, $expected)) { + return; + } + } + + throw new SftpImportException('SSH host key mismatch for ' . $host . ' — refusing to connect'); + } + + /** + * Reads the upload marker, probing each acceptable form of the configured + * remote directory. Some SFTP servers (e.g. Hetzner Storage Box) only accept + * paths relative to the login directory and silently fail on a leading slash. + * + * @return array{0:string,1:?string} the directory form that worked, and the marker bytes (null if none did) + */ + private function readRemoteMarker(SFTP $sftp, string $configuredPath): array + { + $candidates = $this->remoteDirCandidates($configuredPath); + + foreach ($candidates as $dir) { + try { + $contents = $sftp->get($this->joinRemote($dir, self::MARKER_FILE)); + } catch (\Throwable $e) { + throw new SftpImportException( + 'Failed to read remote ' . self::MARKER_FILE . ': ' . $e->getMessage(), + 0, + $e + ); + } + + if ($contents !== false) { + return [$dir, (string)$contents]; + } + } + + return [$candidates[0], null]; + } + + /** + * @return string[] non-empty, unique + */ + private function remoteDirCandidates(string $configuredPath): array + { + $configured = trim($configuredPath); + $relative = ltrim($configured, '/'); + + $candidates = []; + if ($configured !== '' && $configured !== '/') { + $candidates[] = $configured; + } + if ($relative !== '' && $relative !== $configured) { + $candidates[] = $relative; + } + $candidates[] = '.'; + + return array_values(array_unique($candidates)); + } + + /** + * Download Ziel_*.xml + zustiege.xml + uebertragung.info into a fresh temp dir, + * verifying each file's size against the remote listing. The marker is written + * last, from the bytes already fetched for the freshness check. + * + * @throws SftpImportException + */ + private function downloadInto(SFTP $sftp, string $remotePath, string $markerBytes): string + { + $list = $sftp->rawlist($remotePath, false); + if ($list === false) { + throw new SftpImportException('Cannot list remote directory ' . $remotePath); + } + + $productFiles = []; + $pickupSize = null; + foreach ($list as $name => $attrs) { + if ($name === '.' || $name === '..' || !is_array($attrs)) { + continue; + } + if ((int)($attrs['type'] ?? 0) === self::REMOTE_TYPE_DIRECTORY) { + continue; + } + if (strpos($name, self::PRODUCT_PREFIX) === 0 && substr($name, -4) === '.xml') { + $productFiles[$name] = (int)($attrs['size'] ?? -1); + } elseif ($name === self::PICKUPS_FILE) { + $pickupSize = (int)($attrs['size'] ?? -1); + } + } + + if (count($productFiles) === 0) { + throw new SftpImportException('Remote export contains no ' . self::PRODUCT_PREFIX . '_*.xml files'); + } + + $previousCount = $this->stagedProductFileCount(); + if ($previousCount > 0 && count($productFiles) < (int)floor($previousCount * 0.5)) { + throw new SftpImportException(sprintf( + 'Remote export looks truncated (%d %s_*.xml files vs %d previously) — refusing to import', + count($productFiles), + self::PRODUCT_PREFIX, + $previousCount + )); + } + + $toDownload = $productFiles; + if ($pickupSize !== null) { + $toDownload[self::PICKUPS_FILE] = $pickupSize; + } else { + $this->logWarning('remote export has no ' . self::PICKUPS_FILE . '; pickups will be skipped'); + } + + $tmp = $this->newTempDir(); + + foreach ($toDownload as $name => $expectedSize) { + $target = $tmp . '/' . $name; + try { + $ok = $sftp->get($this->joinRemote($remotePath, $name), $target); + } catch (\Throwable $e) { + GeneralUtility::rmdir($tmp, true); + throw new SftpImportException('Download failed for ' . $name . ': ' . $e->getMessage(), 0, $e); + } + + if ($ok === false || !is_file($target)) { + GeneralUtility::rmdir($tmp, true); + throw new SftpImportException('Download failed for ' . $name); + } + + clearstatcache(true, $target); + if ($expectedSize >= 0 && filesize($target) !== $expectedSize) { + $actualSize = filesize($target); + GeneralUtility::rmdir($tmp, true); + throw new SftpImportException(sprintf( + 'Size mismatch for %s (got %d bytes, expected %d)', + $name, + $actualSize, + $expectedSize + )); + } + } + + // Write the completion marker last, mirroring the supplier's upload order. + GeneralUtility::writeFile($tmp . '/' . self::MARKER_FILE, $markerBytes, false); + + return $tmp; + } + + private function stagedProductFileCount(): int + { + $dir = $this->stagingBasePath() . '/' . self::PUBLISHED_DIR; + if (!is_dir($dir)) { + return 0; + } + + $count = 0; + foreach ((array)scandir($dir) as $name) { + if (strpos((string)$name, self::PRODUCT_PREFIX) === 0 && substr((string)$name, -4) === '.xml') { + $count++; + } + } + + return $count; + } + + private function parseMarkerTimestamp(string $contents): ?\DateTime + { + $line = trim((string)strtok($contents, "\r\n")); + if ($line === '') { + return null; + } + + $date = \DateTime::createFromFormat(self::MARKER_TS_FORMAT, $line); + + return $date instanceof \DateTime ? $date : null; + } + + private function logWarning(string $message, array $context = []): void + { + if ($this->logger !== null) { + $this->logger->warning($message, $context); + } + } + + private function stagingBasePath(): string + { + return Environment::getVarPath() . '/transient/' . self::STAGING_SUBDIR; + } + + private function newTempDir(): string + { + $dir = $this->stagingBasePath() . '/.tmp-' . getmypid() . '-' . bin2hex(random_bytes(4)); + GeneralUtility::mkdir_deep($dir); + if (!is_dir($dir) || !is_writable($dir)) { + throw new SftpImportException('Cannot create staging directory: ' . $dir); + } + + return $dir; + } + + private function cleanupStale(): void + { + $base = $this->stagingBasePath(); + if (!is_dir($base)) { + return; + } + + $stale = array_merge( + (array)glob($base . '/.tmp-*', GLOB_ONLYDIR), + (array)glob($base . '/' . self::PUBLISHED_DIR . '.old-*', GLOB_ONLYDIR) + ); + foreach ($stale as $dir) { + GeneralUtility::rmdir($dir, true); + } + } + + private function joinRemote(string $base, string $name): string + { + return rtrim($base, '/') . '/' . ltrim($name, '/'); + } + + /** + * @return array{host:string,port:int,user:string,password:string,remotePath:string,timeout:int,hostKey:string} + */ + private function buildConfig(): array + { + if ($this->configuration === null) { + $raw = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('ep_products'); + $raw = is_array($raw) ? $raw : []; + + $host = trim((string)($raw['productImportSftpHost'] ?? '')); + $user = trim((string)($raw['productImportSftpUser'] ?? '')); + $password = (string)($raw['productImportSftpPassword'] ?? ''); + $remotePath = trim((string)($raw['productImportSftpRemotePath'] ?? '/')); + $port = (int)($raw['productImportSftpPort'] ?? 22); + $timeout = (int)($raw['productImportSftpTimeout'] ?? 15); + $hostKey = trim((string)($raw['productImportSftpHostKey'] ?? '')); + + if ($host === '' || $user === '') { + throw new SftpImportException( + 'SFTP product import is not configured: host and username are required ' + . '(Admin Tools → Settings → Extension Configuration → ep_products).' + ); + } + if ($password === '') { + throw new SftpImportException('SFTP product import is not configured: password is required.'); + } + + $this->configuration = [ + 'host' => $host, + 'port' => $port > 0 ? $port : 22, + 'user' => $user, + 'password' => $password, + 'remotePath' => $remotePath === '' ? '/' : $remotePath, + 'timeout' => $timeout > 0 ? $timeout : 15, + 'hostKey' => $hostKey, + ]; + } + + return $this->configuration; + } +} diff --git a/packages/ep_products/Classes/Service/SnowreportImportService.php b/packages/ep_products/Classes/Service/SnowreportImportService.php new file mode 100644 index 0000000..26ce048 --- /dev/null +++ b/packages/ep_products/Classes/Service/SnowreportImportService.php @@ -0,0 +1,621 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Snowreport; +use EP\EpProducts\Domain\Model\Webcam; +use EP\EpProducts\Domain\Repository\RegionRepository; +use EP\EpProducts\Domain\Repository\SnowreportRepository; +use EP\EpProducts\Domain\Repository\WebcamRepository; +use EP\EpProducts\Utility\SettingsUtility; +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; +use TYPO3\CMS\Core\Http\RequestFactory; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface; + +class SnowreportImportService implements SingletonInterface +{ + const XML_FILE_PATH = 'fileadmin/snowreport/snowreport.xml'; + const XML_FILE_URL = 'https://www.skiresort-service.com/xml-feed/'; + + /** + * Connection and read timeout for the feed download, in seconds. A healthy response to this + * endpoint arrives in well under a second, so the ceiling is kept low enough that an outage on + * the vendor side fails visibly instead of stalling the scheduler task or the backend request. + */ + const HTTP_CONNECT_TIMEOUT = 5; + const HTTP_TIMEOUT = 30; + + /** + * @var array + */ + protected $validSnowreportUids = []; + + /** + * @var array + */ + protected $validWebcamUids = []; + + /** + * Existing records keyed by vendor_uid, preloaded once per import to avoid a query per record. + * + * @var array + */ + protected $snowreportIndex = []; + + /** + * @var array + */ + protected $webcamIndex = []; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager + */ + protected $persistenceManager; + + /** + * @var \EP\EpProducts\Domain\Repository\SnowreportRepository + */ + protected $snowreportRepository; + + /** + * @var \EP\EpProducts\Domain\Repository\WebcamRepository + */ + protected $webcamRepository; + + /** + * @var \EP\EpProducts\Domain\Repository\RegionRepository + */ + protected $regionRepository; + + /** + * @param PersistenceManagerInterface $persistenceManager + * @param SnowreportRepository $snowreportRepository + * @param WebcamRepository $webcamRepository + * @param RegionRepository $regionRepository + */ + public function __construct( + PersistenceManagerInterface $persistenceManager, + SnowreportRepository $snowreportRepository, + WebcamRepository $webcamRepository, + RegionRepository $regionRepository + ) { + $this->persistenceManager = $persistenceManager; + $this->snowreportRepository = $snowreportRepository; + $this->webcamRepository = $webcamRepository; + $this->regionRepository = $regionRepository; + } + + const TYPE_INT = 'int'; + const TYPE_STRING = 'string'; + const TYPE_BOOL = 'bool'; + const TYPE_DATE = 'date'; + + /** + * Declared schema of the vendor feed: XML element name => target column and its type. + * + * XML carries no type information - every element is a string - so the type of each field is + * declared here rather than guessed from the value. Values are converted strictly according to + * this table; anything unexpected is logged and falls back to the type's empty value instead of + * silently changing type. + * + * @var array + */ + protected static $map = [ + 'region_id' => ['column' => 'vendor_uid', 'type' => self::TYPE_INT], + 'region_name' => ['column' => 'region_name', 'type' => self::TYPE_STRING], + 'region_offen' => ['column' => 'opened', 'type' => self::TYPE_BOOL], + 'gletschergebiet' => ['column' => 'glacier', 'type' => self::TYPE_BOOL], + 'schneehoehe_berg' => ['column' => 'snow_level_mountain', 'type' => self::TYPE_INT], + 'schneehoehe_tal' => ['column' => 'snow_level_valley', 'type' => self::TYPE_INT], + 'schneequalitaet' => ['column' => 'snow_quality', 'type' => self::TYPE_STRING], + 'letzter_schneefall' => ['column' => 'last_snowfall', 'type' => self::TYPE_DATE], + 'lifte_gesamt' => ['column' => 'lifts_total', 'type' => self::TYPE_INT], + 'offene_lifte' => ['column' => 'lifts_opened', 'type' => self::TYPE_INT], + 'letzte_aktualisierung' => ['column' => 'last_update', 'type' => self::TYPE_DATE], + 'hoehemax' => ['column' => 'height_max', 'type' => self::TYPE_INT], + 'hoehemin' => ['column' => 'height_min', 'type' => self::TYPE_INT], + 'datum_saisonstart' => ['column' => 'season_start', 'type' => self::TYPE_DATE], + 'datum_saisonende' => ['column' => 'season_end', 'type' => self::TYPE_DATE], + ]; + + /** + * Accepted boolean representations, lowercased. + * + * @var array + */ + protected static $booleanMap = [ + 'true' => true, + '1' => true, + 'ja' => true, + 'yes' => true, + 'false' => false, + '0' => false, + 'nein' => false, + 'no' => false, + ]; + + /** + * Distinct conversion problems encountered during the current import. + * + * @var array + */ + protected $conversionWarnings = []; + + /** + * @return int + */ + public function importSnowReportData() + { + $count = 0; + $file = GeneralUtility::getFileAbsFileName(self::XML_FILE_PATH); + + if (!is_file($file)) { + $this->writelog(sprintf('Snowreport XML import failed: file "%s" does not exist.', $file)); + + return 0; + } + + try { + $xmlData = $this->parseReportsXml((string) file_get_contents($file)); + } catch (\RuntimeException $e) { + $this->writelog(sprintf('Snowreport XML import failed: %s', $e->getMessage())); + + return 0; + } + + $this->resetImportState(); + $this->buildIndexes(); + + $snowReportStoragePid = $this->getStoragePid(); + + foreach ($xmlData->schneemeldung as $reportXml) + { + $vendorUid = (int) $reportXml->region_id; + $snowreport = $this->getSnowreport($vendorUid); + + $row = []; + foreach (static::$map as $key => $field) + { + if (!isset($reportXml->{$key})) { + $this->addConversionWarning(sprintf('Element <%s> missing in feed.', $key)); + $row[$field['column']] = $this->getEmptyValue($field['type']); + continue; + } + + $row[$field['column']] = $this->convertValue((string) $reportXml->{$key}, $field['type'], $key); + } + + $snowreport->setPid($snowReportStoragePid); + $snowreport->setVendorUid($vendorUid); + $snowreport->setRegionName($row['region_name']); + $snowreport->setOpened($row['opened']); + $snowreport->setGlacier($row['glacier']); + $snowreport->setSnowLevelMountain($row['snow_level_mountain']); + $snowreport->setSnowLevelValley($row['snow_level_valley']); + $snowreport->setSnowQuality($row['snow_quality']); + $snowreport->setLastSnowfall($row['last_snowfall']); + $snowreport->setLiftsTotal($row['lifts_total']); + $snowreport->setLiftsOpened($row['lifts_opened']); + $snowreport->setLastUpdate($row['last_update']); + $snowreport->setHeightMax($row['height_max']); + $snowreport->setHeightMin($row['height_min']); + $snowreport->setSeasonStart($row['season_start']); + $snowreport->setSeasonEnd($row['season_end']); + + if ($reportXml->webcam) { + $this->importWebcamData($reportXml->webcam, $snowreport); + } + + // New objects are already registered via add(); only managed ones need update(). + if (!$snowreport->_isNew()) { + $this->snowreportRepository->update($snowreport); + } + + $this->validSnowreportUids[] = $vendorUid; + + $count++; + } + + $this->persistenceManager->persistAll(); + + $this->cleanupData('tx_epproducts_domain_model_webcam', $this->validWebcamUids); + $this->cleanupData('tx_epproducts_domain_model_snowreport', $this->validSnowreportUids); + $this->updateWebcamCounters(); + + $this->logConversionWarnings(); + $this->writelog(sprintf('Snowreport XML import successful (%d records).', $count)); + + return $count; + } + + /** + * @param \SimpleXMLElement $xml + * @param Snowreport $snowreport + */ + public function importWebcamData(\SimpleXMLElement $xml, Snowreport $snowreport) + { + $snowReportStoragePid = $this->getStoragePid(); + foreach ($xml->children() as $item) + { + $include = $this->convertValue((string) $item->aktuell, self::TYPE_BOOL, 'aktuell'); + $available = $this->convertValue((string) $item->verf, self::TYPE_BOOL, 'verf'); + $vendorUid = (int) $item->uid; + + $webcam = isset($this->webcamIndex[$vendorUid]) ? $this->webcamIndex[$vendorUid] : null; + + // Skip cams that are neither current nor already known + if (null === $webcam && false === $include) { + continue; + } + + // Add new cam if not in db yet + if (null === $webcam) { + $webcam = new Webcam(); + $webcam->setVendorUid($vendorUid); + $webcam->setPid($snowReportStoragePid); + $this->webcamRepository->add($webcam); + $this->webcamIndex[$vendorUid] = $webcam; + $snowreport->addWebcam($webcam); + } + + $webcam->setUrl((string) $item->url); + $webcam->setName((string) $item->dt); + $webcam->setAvailable($available && $include); + + $this->validWebcamUids[] = $vendorUid; + } + } + + /** + * Flags records that are no longer contained in the feed as deleted. + * + * @param string $table + * @param array $validUids + * @return int Number of records flagged + */ + public function cleanupData($table, array $validUids) + { + /** @var ConnectionPool $connectionPool */ + $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); + $qb = $connectionPool->getQueryBuilderForTable($table); + // Hidden records are still managed by the import, only deleted ones are out of scope. + $qb->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)) + ; + $result = $qb + ->select('vendor_uid') + ->from($table) + ->execute() + ->fetchAllAssociative() + ; + + $currentUids = array_map(function ($row) { + return (int)$row['vendor_uid']; + }, $result); + + $obsoleteUids = array_values(array_diff($currentUids, $validUids)); + if (0 === count($obsoleteUids)) { + return 0; + } + + $qb = $connectionPool->getQueryBuilderForTable($table); + + return (int) $qb + ->update($table) + ->set('deleted', 1) + ->set('tstamp', isset($GLOBALS['EXEC_TIME']) ? $GLOBALS['EXEC_TIME'] : time()) + ->where( + $qb->expr()->in( + 'vendor_uid', + $qb->createNamedParameter($obsoleteUids, Connection::PARAM_INT_ARRAY) + ) + ) + ->execute() + ; + } + + /** + * Recalculates the inline child counter on the snowreport records. Webcams are flagged as deleted + * with plain SQL, which bypasses the counter Extbase maintains for the relation. + */ + protected function updateWebcamCounters() + { + $connection = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionForTable('tx_epproducts_domain_model_snowreport'); + + $connection->executeStatement( + 'UPDATE tx_epproducts_domain_model_snowreport s SET s.webcams = (' + . ' SELECT COUNT(*) FROM tx_epproducts_domain_model_webcam w' + . ' WHERE w.snowreport = s.uid AND w.deleted = 0' + . ')' + ); + } + + /** + * Downloads the feed and replaces the local file only if the response is a usable XML document. + * The existing file is left untouched on any failure. + * + * @return bool + */ + public function downloadReportsXml() + { + $path = GeneralUtility::getFileAbsFileName(self::XML_FILE_PATH); + + try { + $xmlContent = $this->fetchReportsXml(); + $this->parseReportsXml($xmlContent); + } catch (\Exception $e) { + $this->writelog(sprintf( + 'Snowreport XML download failed (%s). Keeping existing file.', + $e->getMessage() + )); + + return false; + } + + // Write to a temporary file first and move it into place, so the feed is either the old or the + // new document, never a partially written one. + $temporaryPath = $path . '.tmp'; + if (false === file_put_contents($temporaryPath, $xmlContent) || !rename($temporaryPath, $path)) { + @unlink($temporaryPath); + $this->writelog(sprintf('Snowreport XML could not be written to "%s".', $path)); + + return false; + } + + return true; + } + + /** + * @return string + * @throws \RuntimeException + */ + protected function fetchReportsXml() + { + /** @var RequestFactory $requestFactory */ + $requestFactory = GeneralUtility::makeInstance(RequestFactory::class); + + $response = $requestFactory->request(self::XML_FILE_URL, 'GET', [ + 'connect_timeout' => self::HTTP_CONNECT_TIMEOUT, + 'timeout' => self::HTTP_TIMEOUT, + 'headers' => [ + 'Accept' => 'application/xml, text/xml', + ], + ]); + + if (200 !== $response->getStatusCode()) { + throw new \RuntimeException( + sprintf('unexpected status code %d', $response->getStatusCode()), + 1758000001 + ); + } + + return (string) $response->getBody(); + } + + /** + * Parses the feed and rejects documents that are unusable. Without this an empty or truncated + * response would be imported as an empty feed, and the cleanup would flag every record as deleted. + * + * @param string $xmlContent + * @return \SimpleXMLElement + * @throws \RuntimeException + */ + protected function parseReportsXml($xmlContent) + { + if ('' === trim($xmlContent)) { + throw new \RuntimeException('response is empty', 1758000002); + } + + $useInternalErrors = libxml_use_internal_errors(true); + libxml_clear_errors(); + $xmlData = simplexml_load_string($xmlContent); + $errors = libxml_get_errors(); + libxml_clear_errors(); + libxml_use_internal_errors($useInternalErrors); + + if (false === $xmlData) { + $message = count($errors) > 0 ? trim($errors[0]->message) : 'unknown error'; + throw new \RuntimeException(sprintf('response is not valid XML (%s)', $message), 1758000003); + } + + if (0 === $xmlData->schneemeldung->count()) { + throw new \RuntimeException('response contains no elements', 1758000004); + } + + return $xmlData; + } + + /** + * Loads existing records once, keyed by vendor_uid. Replaces a repository lookup per feed record. + */ + protected function buildIndexes() + { + $this->snowreportIndex = []; + foreach ($this->snowreportRepository->findAll() as $snowreport) { + $this->snowreportIndex[(int) $snowreport->getVendorUid()] = $snowreport; + } + + $this->webcamIndex = []; + foreach ($this->webcamRepository->findAll() as $webcam) { + $this->webcamIndex[(int) $webcam->getVendorUid()] = $webcam; + } + } + + /** + * @param int $vendorUid + * @return Snowreport + */ + protected function getSnowreport($vendorUid) + { + if (isset($this->snowreportIndex[$vendorUid])) { + return $this->snowreportIndex[$vendorUid]; + } + + $snowreport = new Snowreport(); + $snowreport->setVendorUid($vendorUid); + $this->snowreportRepository->add($snowreport); + $this->snowreportIndex[$vendorUid] = $snowreport; + + return $snowreport; + } + + /** + * The service is a singleton, so per-import state must not survive into the next run. + */ + protected function resetImportState() + { + $this->validSnowreportUids = []; + $this->validWebcamUids = []; + $this->conversionWarnings = []; + } + + /** + * Converts a raw XML string to the declared type of the field. + * + * @param string $raw + * @param string $type + * @param string $key XML element name, for logging + * @return int|string|bool + */ + protected function convertValue($raw, $type, $key) + { + $raw = trim($raw); + + // The feed delivers empty elements for data that is not available yet (snow levels out of + // season, for example). That is expected, not an error. + if ($raw === '') { + return $this->getEmptyValue($type); + } + + switch ($type) { + case self::TYPE_STRING: + return $raw; + + case self::TYPE_INT: + if (!preg_match('/^-?\d+$/', $raw)) { + $this->addConversionWarning(sprintf('<%s> expected an integer, got "%s".', $key, $raw)); + + return 0; + } + + return (int) $raw; + + case self::TYPE_BOOL: + $normalized = strtolower($raw); + if (!array_key_exists($normalized, static::$booleanMap)) { + $this->addConversionWarning(sprintf('<%s> expected a boolean, got "%s".', $key, $raw)); + + return false; + } + + return static::$booleanMap[$normalized]; + + case self::TYPE_DATE: + if (!preg_match('/^\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}(:\d{2})?)?$/', $raw)) { + $this->addConversionWarning(sprintf('<%s> expected a date, got "%s".', $key, $raw)); + + return 0; + } + + $date = new \DateTime($raw); + + return $date->getTimestamp(); + } + + throw new \InvalidArgumentException(sprintf('Unknown field type "%s" declared for <%s>.', $type, $key), 1758000000); + } + + /** + * @param string $type + * @return int|string|bool + */ + protected function getEmptyValue($type) + { + if ($type === self::TYPE_STRING) { + return ''; + } + if ($type === self::TYPE_BOOL) { + return false; + } + + return 0; + } + + /** + * @param string $message + */ + protected function addConversionWarning($message) + { + // Keyed to keep one entry per distinct problem instead of one per record. + $this->conversionWarnings[$message] = true; + } + + /** + * Writes a single summary entry to the log if the feed contained unexpected values. + */ + protected function logConversionWarnings() + { + if (0 === count($this->conversionWarnings)) { + return; + } + + $messages = array_keys($this->conversionWarnings); + $this->writelog(sprintf( + 'Snowreport XML import: %d unexpected value(s) in feed: %s', + count($messages), + implode(' | ', array_slice($messages, 0, 10)) + )); + + $this->conversionWarnings = []; + } + + /** + * The scheduler task and the backend module provide a backend user, the CLI command does not. + * + * @param string $message + */ + protected function writelog($message) + { + if (isset($GLOBALS['BE_USER']) && is_object($GLOBALS['BE_USER'])) { + $GLOBALS['BE_USER']->writelog(4, 0, 0, 0, $message, []); + } + } + + public function getStoragePid() + { + $settingsUtility = new SettingsUtility(); + + return $settingsUtility->get('snowReportStoragePid'); + } +} diff --git a/packages/ep_products/Classes/Service/XmlProductReader.php b/packages/ep_products/Classes/Service/XmlProductReader.php new file mode 100644 index 0000000..1832bba --- /dev/null +++ b/packages/ep_products/Classes/Service/XmlProductReader.php @@ -0,0 +1,369 @@ +path = rtrim($path, '/'); + $this->logger = $logger ?? new NullLogger(); + $this->timezone = new \DateTimeZone(self::TIMEZONE); + $this->wallClock = new \DateTimeZone('UTC'); + } + + public function getPath(): string + { + return $this->path; + } + + /** + * The export's own timestamp from uebertragung.info, or null if unreadable. + */ + public function getSourceGeneratedAt(): ?\DateTimeImmutable + { + $file = $this->path . '/' . self::MARKER_FILE; + if (!is_file($file)) { + return null; + } + + $line = trim((string) strtok((string) file_get_contents($file), "\r\n")); + $date = \DateTimeImmutable::createFromFormat(self::MARKER_TS_FORMAT, $line, $this->timezone); + + return $date ?: null; + } + + /** + * @return \Generator + */ + public function getIterator(): \Generator + { + if ($this->pickups === null) { + $this->pickups = $this->readPickups(); + } + + $folder = opendir($this->path); + if ($folder === false) { + $this->logger->error('product xml directory not readable', ['path' => $this->path]); + return; + } + + try { + while (($fileName = readdir($folder)) !== false) { + if (strpos($fileName, self::PRODUCT_PREFIX) === 0) { + yield from $this->readProductFile($this->path . '/' . $fileName); + } + } + } finally { + closedir($folder); + } + } + + private function readProductFile(string $file): \Generator + { + libxml_use_internal_errors(true); + $xmlData = simplexml_load_string((string) file_get_contents($file)); + if ($xmlData === false) { + libxml_clear_errors(); + $this->logger->error('product xml invalid', ['file' => $file]); + return; + } + + $xmlProduct = $xmlData->reise[0]; + if ($xmlProduct === null) { + return; + } + + $product = [ + 'code' => (string) $xmlProduct['code'], + 'busProId' => (int) $xmlProduct['idbuspro'], + ]; + + foreach ($xmlProduct->termin as $xmlDate) { + $from = $this->parseDate((string) $xmlDate['termin']); + $to = $this->parseDate((string) $xmlDate['bis']); + if ($from === null || $to === null) { + $this->logger->warning('travel date skipped, dates unparseable', [ + 'code' => (string) $xmlDate['code'], + 'termin' => (string) $xmlDate['termin'], + 'bis' => (string) $xmlDate['bis'], + ]); + continue; + } + + $date = [ + 'code' => (string) $xmlDate['code'], + 'busProId' => (int) $xmlDate['idbuspro'], + 'from' => $from->format('Y-m-d'), + 'to' => $to->format('Y-m-d'), + 'type' => (string) $xmlDate['reiseart'], + ]; + + // Everything below is per travel date and shared by its hotels. + $selectionGroups = $this->readSelectionGroups($xmlDate); + $transportServices = $this->readServices($xmlDate, 'lei_befoerderung'); + $dateServices = $this->readServices($xmlDate, 'lei_sonstiges'); + $pickups = $this->readDatePickups($xmlDate, $from); + $guide = $this->readGuide($xmlDate); + + foreach ($xmlDate->hotel as $xmlHotel) { + [$pseudoPrice, $rooms] = $this->readRooms($xmlHotel); + + yield [ + 'product' => $product, + 'date' => $date, + 'hotel' => [ + 'code' => (string) $xmlHotel['code'], + 'busProId' => (int) $xmlHotel['idbuspro'], + ], + 'pseudoPrice' => $pseudoPrice, + 'selectionGroups' => $selectionGroups, + 'transportServices' => $transportServices, + 'dateServices' => $dateServices, + 'hotelServices' => $this->readServices($xmlHotel, 'lei_sonstiges'), + 'rooms' => $rooms, + 'pickups' => $pickups, + 'guide' => $guide, + ]; + } + } + } + + private function readSelectionGroups(\SimpleXMLElement $xmlDate): array + { + $groups = []; + foreach ($xmlDate->selektiongruppe as $xmlGroup) { + $selections = []; + foreach ($xmlGroup->selektion as $xmlSelection) { + $selections[] = [ + 'busProId' => (int) $xmlSelection['idbuspro'], + 'label' => (string) $xmlSelection['bezeichnung'], + ]; + } + $groups[] = [ + 'busProId' => (int) $xmlGroup['idbuspro'], + 'label' => (string) $xmlGroup['bezeichnung'], + 'selections' => $selections, + ]; + } + + return $groups; + } + + private function readServices(\SimpleXMLElement $xmlParent, string $list): array + { + $services = []; + foreach ($xmlParent->xpath($list . '/leistung') as $xmlService) { + $direction = trim((string) $xmlService->richtung); + $services[] = [ + 'busProId' => (int) $xmlService['idbuspro'], + 'subType' => (string) $xmlService['unterart'], + 'label' => $this->normalizeWhitespace((string) $xmlService->text), + 'price' => $this->parsePrice((string) $xmlService->preis), + 'status' => (string) $xmlService->status, + 'direction' => $direction === '' ? null : $direction, + ]; + } + + return $services; + } + + /** + * @return array{0: ?float, 1: array} + */ + private function readRooms(\SimpleXMLElement $xmlHotel): array + { + $pseudoPrice = null; + $rooms = []; + foreach ($xmlHotel->xpath('zimmer/preis') as $xmlRoom) { + $code = trim((string) $xmlRoom['zimmercode']); + if ($code === self::CODE_PSEUDOPRICE) { + $pseudoPrice = $this->parsePrice((string) $xmlRoom['preis']); + continue; + } + + $rooms[] = [ + 'busProId' => (int) $xmlRoom['idbuspro_zimmer'], + 'code' => $code, + 'label' => (string) $xmlRoom['zimmertext'], + 'category' => (string) $xmlRoom['kat'], + 'minPax' => $this->parseInt((string) $xmlRoom['MinPax']), + 'maxPax' => $this->parseInt((string) $xmlRoom['MaxPax']), + 'nights' => $this->parseInt((string) $xmlRoom['naechte']), + 'price' => $this->parsePrice((string) $xmlRoom['preis']), + 'available' => $this->parseInt((string) $xmlRoom['verfuegbar']), + 'status' => (string) $xmlRoom['status'], + 'boardCode' => (string) $xmlRoom['vpcode'], + 'boardBusProId' => $this->parseInt((string) $xmlRoom['idbuspro_vp']), + ]; + } + + return [$pseudoPrice, $rooms]; + } + + /** + * A bare HH:mm takes the travel date's start, and is rolled onto the next + * day when it would otherwise fall before the latest full date-time listed + * before it: a coach does not board backwards. The result is the local + * date and time as BusPro states it, without an offset. + */ + private function readDatePickups(\SimpleXMLElement $xmlDate, \DateTimeImmutable $from): array + { + $pickups = []; + $latest = null; + foreach ($xmlDate->xpath('zustiege/zustieg') as $xmlPickup) { + $busProId = (int) $xmlPickup['idbuspro']; + $time = trim((string) $xmlPickup['zeit']); + + $datetime = null; + if ($time !== '' && strpos($time, ' ') !== false) { + $datetime = \DateTimeImmutable::createFromFormat('!d.m.Y H:i:s', $time, $this->wallClock) ?: null; + if ($datetime !== null && ($latest === null || $datetime > $latest)) { + $latest = $datetime; + } + } elseif ($time !== '') { + $datetime = \DateTimeImmutable::createFromFormat('!Y-m-d H:i', $from->format('Y-m-d') . ' ' . $time, $this->wallClock) ?: null; + if ($datetime !== null && $latest !== null && $datetime < $latest) { + $datetime = $datetime->modify('+1 day'); + } + } + + $masterData = $this->pickups[$busProId] ?? null; + $pickups[] = [ + 'busProId' => $busProId, + 'code' => $masterData['code'] ?? null, + 'city' => $masterData['city'] ?? null, + 'street' => $masterData['street'] ?? null, + 'price' => $this->parsePrice((string) $xmlPickup['preis']), + 'datetime' => $datetime !== null ? $datetime->format(self::LOCAL_DATETIME_FORMAT) : null, + ]; + } + + return $pickups; + } + + private function readGuide(\SimpleXMLElement $xmlDate): ?array + { + foreach ($xmlDate->xpath('lei_befoerderung/leistung[@unterart="BUS"]') as $xmlService) { + if ((string) $xmlService->richtung === 'RUECK') { + continue; + } + if (!isset($xmlService->zustiegsplanung->reiseleiter)) { + continue; + } + + return [ + 'name' => (string) $xmlService->zustiegsplanung->reiseleiter->name, + 'phone' => (string) $xmlService->zustiegsplanung->reiseleiter->telefon, + ]; + } + + return null; + } + + /** + * @return array + */ + private function readPickups(): array + { + $file = $this->path . '/' . self::PICKUPS_FILE; + libxml_use_internal_errors(true); + if (!is_file($file) || !$xmlData = simplexml_load_string((string) file_get_contents($file))) { + libxml_clear_errors(); + $this->logger->warning('pickup xml missing or invalid', ['file' => $file]); + return []; + } + + $pickups = []; + foreach ($xmlData->zustieg as $xmlPickup) { + $pickups[(int) $xmlPickup['idbuspro']] = [ + 'code' => (string) $xmlPickup['code'], + 'city' => $this->normalizeWhitespace((string) $xmlPickup->ort), + 'street' => $this->normalizeWhitespace((string) $xmlPickup->strasse), + ]; + } + + return $pickups; + } + + private function normalizeWhitespace(string $value): string + { + return trim((string) preg_replace('/\s+/u', ' ', $value)); + } + + private function parseDate(string $value): ?\DateTimeImmutable + { + $date = \DateTimeImmutable::createFromFormat('!d.m.Y', trim($value), $this->timezone); + + return $date ?: null; + } + + /** + * German format with an optional thousands separator: "1.234,50" is 1234.5. + */ + private function parsePrice(string $value): ?float + { + $value = str_replace(['.', ','], ['', '.'], trim($value)); + + return is_numeric($value) ? (float) $value : null; + } + + private function parseInt(string $value): ?int + { + $value = trim($value); + + return is_numeric($value) ? (int) $value : null; + } +} diff --git a/packages/ep_products/Classes/Task/ImportContingentsTask.php b/packages/ep_products/Classes/Task/ImportContingentsTask.php new file mode 100644 index 0000000..b9aeaa2 --- /dev/null +++ b/packages/ep_products/Classes/Task/ImportContingentsTask.php @@ -0,0 +1,49 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\ContingentImportService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Scheduler\Task\AbstractTask; + +class ImportContingentsTask extends AbstractTask +{ + public function execute() + { + /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + /** @var \EP\EpProducts\Service\ContingentImportService $importService */ + $importService = $objectManager->get(ContingentImportService::class); + if ($importService->isImportRequired()) { + $importService->import(); + } + + return true; + } +} diff --git a/packages/ep_products/Classes/Task/ImportDatesTask.php b/packages/ep_products/Classes/Task/ImportDatesTask.php new file mode 100644 index 0000000..d1edeb9 --- /dev/null +++ b/packages/ep_products/Classes/Task/ImportDatesTask.php @@ -0,0 +1,74 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\DateImportService; +use EP\EpProducts\Service\ProductImportSourceFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManager; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; +use TYPO3\CMS\Extbase\Service\CacheService; +use TYPO3\CMS\Scheduler\Task\AbstractTask; + +class ImportDatesTask extends AbstractTask +{ + public function execute(): bool + { + $importService = GeneralUtility::makeInstance(DateImportService::class); + $importSource = GeneralUtility::makeInstance(ProductImportSourceFactory::class)->get(); + $cacheService = GeneralUtility::makeInstance(CacheService::class); + + // Acquire the export only when it is newer than the one we last imported + // (SFTP mode: download to a staging dir; local mode: fileadmin/xmlexport; + // API mode: walk the MyE&P export pages). An acquisition failure is left + // to propagate so the scheduler marks the task failed. + $import = $importSource->acquire(); + + if ($import !== null) { + $importService->import($import->getRows()); + // Advance the "last imported" bookkeeping only after a successful + // import, so a crashed import retries on the next run. + $importSource->markImported($import); + } + + $settings = $this->getSettings(); + $travelInfoPageUid = $settings['travelInfoPageUid']; + $cacheService->clearPageCache($travelInfoPageUid); + + return true; + } + + protected function getSettings(): array + { + $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); + $settings = $configurationManager + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); + + return $settings['plugin.']['tx_epproducts.']['settings.']; + } +} diff --git a/packages/ep_products/Classes/Task/ImportSnowreportTask.php b/packages/ep_products/Classes/Task/ImportSnowreportTask.php new file mode 100644 index 0000000..ea8dcda --- /dev/null +++ b/packages/ep_products/Classes/Task/ImportSnowreportTask.php @@ -0,0 +1,46 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\SnowreportImportService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Scheduler\Task\AbstractTask; + +class ImportSnowreportTask extends AbstractTask +{ + public function execute() + { + /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $snowreportService = $objectManager->get(SnowreportImportService::class); + $snowreportService->downloadReportsXml(); + $snowreportService->importSnowReportData(); + return true; + } +} diff --git a/packages/ep_products/Classes/Traits/DbConnectionTrait.php b/packages/ep_products/Classes/Traits/DbConnectionTrait.php new file mode 100644 index 0000000..27aab94 --- /dev/null +++ b/packages/ep_products/Classes/Traits/DbConnectionTrait.php @@ -0,0 +1,49 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +trait DbConnectionTrait +{ + + /** + * @param string $connectionName + * @return \TYPO3\CMS\Core\Database\Connection + * @throws \Doctrine\DBAL\DBALException + */ + protected function getDbConnection($connectionName = 'Default') + { + /** @var ConnectionPool $connectionPool */ + $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); + + return $connectionPool->getConnectionByName($connectionName); + } + +} diff --git a/packages/ep_products/Classes/Traits/RequestArgumentTypeConversionTrait.php b/packages/ep_products/Classes/Traits/RequestArgumentTypeConversionTrait.php new file mode 100644 index 0000000..f7f72ba --- /dev/null +++ b/packages/ep_products/Classes/Traits/RequestArgumentTypeConversionTrait.php @@ -0,0 +1,53 @@ +get(SearchResultService::class); + + if (!$this->request->hasArgument('searchParams')) { + $searchParams = $searchResultService->getNormalizedSearchParams(); + } else { + $requestArgument = $this->request->getArgument('searchParams'); + $searchParamsFromRequest = is_array($requestArgument) ? + $requestArgument : json_decode($requestArgument, true, 512, JSON_THROW_ON_ERROR); + $searchParams = $searchResultService->getNormalizedSearchParams($searchParamsFromRequest); + } + + $this->request->setArgument('searchParams', $searchParams); + } + + public function processFilterSettingsArgument(array $forcedConceptUids = []): void + { + /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + + /** @var \EP\EpProducts\Service\FilterService $filterService */ + $filterService = $objectManager->get(FilterService::class); + + if (!$this->request->hasArgument('filterSettings')) { + $filterSettings = $filterService->getNormalizedFilterSettings([], $forcedConceptUids); + } else { + $requestArgument = $this->request->getArgument('filterSettings'); + $filterSettingsFromRequest = is_array($requestArgument) ? + $requestArgument : json_decode($requestArgument, true, 512, JSON_THROW_ON_ERROR); + $filterSettings = $this->filterService->getNormalizedFilterSettings($filterSettingsFromRequest, $forcedConceptUids); + } + + $this->request->setArgument('filterSettings', $filterSettings); + } + +} diff --git a/packages/ep_products/Classes/Updates/TeamSlugUpdater.php b/packages/ep_products/Classes/Updates/TeamSlugUpdater.php new file mode 100644 index 0000000..5f1d8e0 --- /dev/null +++ b/packages/ep_products/Classes/Updates/TeamSlugUpdater.php @@ -0,0 +1,121 @@ +countRequiredUpdates(); + + return (bool)$elementCount; + } + + /** + * @return bool + */ + public function executeUpdate(): bool + { + $queryBuilder = $this->getQueryBuilder(); + + $statement = $queryBuilder->select('uid', 'name') + ->from('tx_epproducts_domain_model_teammember') + ->where( + $queryBuilder->expr()->orX( + $queryBuilder->expr()->eq( + 'path_segment', + $queryBuilder->createNamedParameter('') + ), + $queryBuilder->expr()->isNull('path_segment') + ) + ) + ->execute(); + while ($record = $statement->fetch()) { + $slug = Transliterator::urlize($record['name']); + $queryBuilder = $this->getQueryBuilder(); + $queryBuilder->update('tx_epproducts_domain_model_teammember') + ->where( + $queryBuilder->expr()->eq( + 'uid', + $queryBuilder->createNamedParameter($record['uid'], \PDO::PARAM_INT) + ) + ) + ->set('path_segment', $slug); + $queryBuilder->execute(); + } + + return true; + } + + protected function countRequiredUpdates(): int + { + $queryBuilder = $this->getQueryBuilder(); + + return $queryBuilder->count('uid') + ->from('tx_epproducts_domain_model_teammember') + ->where( + $queryBuilder->expr()->orX( + $queryBuilder->expr()->eq( + 'path_segment', + $queryBuilder->createNamedParameter('') + ), + $queryBuilder->expr()->isNull('path_segment') + ) + ) + ->execute()->fetchColumn(); + } + + protected function getQueryBuilder(): QueryBuilder + { + /** @var QueryBuilder $queryBuilder */ + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_epproducts_domain_model_teammember'); + $queryBuilder->getRestrictions()->removeAll(); + + return $queryBuilder; + } + + /** + * @return string[] + */ + public function getPrerequisites(): array + { + return [ + DatabaseUpdatedPrerequisite::class, + ]; + } +} diff --git a/packages/ep_products/Classes/UserFunc/TcaProcFunc.php b/packages/ep_products/Classes/UserFunc/TcaProcFunc.php new file mode 100644 index 0000000..2932e6f --- /dev/null +++ b/packages/ep_products/Classes/UserFunc/TcaProcFunc.php @@ -0,0 +1,48 @@ +getQueryBuilderForTable('tx_epproducts_domain_model_date'); + + $result = $qb + ->select('date_start', 'date_end', 'bus_pro_id') + ->from('tx_epproducts_domain_model_date') + ->where($qb->expr()->eq('product', $productUid)) + ->orderBy('date_start') + ->execute() + ->fetchAll() + ; + + foreach ($result as $row) { + $dateStart = new \DateTime($row['date_start']); + $dateEnd = new \DateTime($row['date_end']); + + $items[] = [ + sprintf('%s - %s', $dateStart->format('d.m.Y'), $dateEnd->format('d.m.Y')), + $row['bus_pro_id'], + ]; + } + + $config['items'] = $items; + + return $config; + } +} \ No newline at end of file diff --git a/packages/ep_products/Classes/Utility/BookingUrlUtility.php b/packages/ep_products/Classes/Utility/BookingUrlUtility.php new file mode 100644 index 0000000..24f4892 --- /dev/null +++ b/packages/ep_products/Classes/Utility/BookingUrlUtility.php @@ -0,0 +1,40 @@ +setDefined(['paCode', 'returnPageUid']); + $resolver->setRequired(['dateId', 'hotelId', 'myEpUrl']); + + $resolvedOptions = $resolver->resolve($options); + + $query = [ + 'date_id' => $resolvedOptions['dateId'], + 'hotel_id' => $resolvedOptions['hotelId'], + ]; + + if (null !== $resolvedOptions['paCode']) { + $query['agency'] = $resolvedOptions['paCode']; + } + + if (null !== $resolvedOptions['returnPageUid'] && 0 < (int) $resolvedOptions['returnPageUid']) { + $siteFinder = GeneralUtility::makeInstance(SiteFinder::class); + $site = $siteFinder->getSiteByPageId((int) $resolvedOptions['returnPageUid']); + + $query['r'] = (string) $site->getRouter()->generateUri((int) $resolvedOptions['returnPageUid'], [], '', RouterInterface::ABSOLUTE_URL); + } + + $myEpUrl = rtrim($resolvedOptions['myEpUrl'], '/'); + + return $myEpUrl . '/bookings/create?' . http_build_query($query); + } +} diff --git a/packages/ep_products/Classes/Utility/DateUtility.php b/packages/ep_products/Classes/Utility/DateUtility.php new file mode 100644 index 0000000..033aaa4 --- /dev/null +++ b/packages/ep_products/Classes/Utility/DateUtility.php @@ -0,0 +1,82 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class DateUtility +{ + + public static function formatDateRange + ( + ?string $dateFromString = '', + ?string $dateToString = '', + bool $includeLabel = true, + string $format = 'd.m.Y' + ): string { + if (empty($dateFromString) && empty($dateToString)) { + return ''; + } + $dateFrom = new \DateTime($dateFromString); + $dateTo = new \DateTime($dateToString); + $label = $includeLabel ? 'im Zeitraum ' : ''; + if ($dateFromString !== '' && $dateToString === '') { + return $label . 'ab ' . $dateFrom->format($format); + } + if ($dateFromString === '' && $dateToString !== '') { + return $label . '-' . $dateTo->format($format); + } + return $label . $dateFrom->format($format) . '-' . $dateTo->format($format); + } + + public static function getDateRange($fromYear = null, $fromMonth = null, int $months = 6): array + { + $now = new \DateTime('now'); + $currentMonth = (int) $now->format('m'); + $currentYear = (int) $now->format('Y'); + + if ($fromYear === null || (int) $fromYear < $currentYear) { + $fromYear = $currentYear; + } + + if ($fromMonth === null || ((int) $fromYear === $currentYear && (int) $fromMonth < $currentMonth)) { + $fromMonth = $currentMonth; + } + + $begin = new \DateTimeImmutable(); + $begin = $begin->setDate((int) $fromYear, (int) $fromMonth, 1)->setTime(0, 0, 0); + $end = $begin->add(new \DateInterval('P' . $months . 'M')); + $period = new Period($begin, $end); + + return [ + 'period' => $period, + 'dateFrom' => $period->getStartDate()->format('Y-m-d'), + 'dateTo' => $period->getEndDate()->sub(new \DateInterval('P1D'))->format('Y-m-d'), + ]; + } +} diff --git a/packages/ep_products/Classes/Utility/HotelIconUtility.php b/packages/ep_products/Classes/Utility/HotelIconUtility.php new file mode 100644 index 0000000..21cb870 --- /dev/null +++ b/packages/ep_products/Classes/Utility/HotelIconUtility.php @@ -0,0 +1,83 @@ + + */ + public static function getIcons(array $hotel): array + { + $icons = []; + + if (($hotel['beds_count'] ?? 0) > 0) { + $icons[] = [ + 'icon' => 'bed', + 'label' => sprintf('%d Betten', $hotel['beds_count']), + ]; + } + if (($hotel['parking_count'] ?? 0) > 0) { + $icons[] = [ + 'icon' => 'parking', + 'label' => sprintf('%d Parkplätze', $hotel['parking_count']), + ]; + } + if ($hotel['altitude'] ?? null) { + $icons[] = [ + 'icon' => 'mountain', + 'label' => $hotel['altitude'], + ]; + } + if ($hotel['distance_bus'] ?? null) { + $icons[] = [ + 'icon' => 'bus', + 'label' => $hotel['distance_bus'], + ]; + } + if ($hotel['distance_lift'] ?? null) { + $icons[] = [ + 'icon' => 'lift', + 'label' => $hotel['distance_lift'], + ]; + } + if ($hotel['shower'] ?? null) { + $icons[] = [ + 'icon' => 'shower', + 'label' => $hotel['shower'], + ]; + } + if ($hotel['partyroom'] ?? false) { + $icons[] = [ + 'icon' => 'party', + 'label' => 'Partyzimmer', + ]; + } + if ($hotel['gamesroom'] ?? false) { + $icons[] = [ + 'icon' => 'dice', + 'label' => 'Spielezimmer', + ]; + } + if ($hotel['sauna'] ?? false) { + $icons[] = [ + 'icon' => 'sauna', + 'label' => 'Sauna', + ]; + } + if ($hotel['wifi'] ?? false) { + $icons[] = [ + 'icon' => 'wifi', + 'label' => 'WiFi', + ]; + } + + return $icons; + } +} diff --git a/packages/ep_products/Classes/Utility/SerializationUtility.php b/packages/ep_products/Classes/Utility/SerializationUtility.php new file mode 100644 index 0000000..02a0ca0 --- /dev/null +++ b/packages/ep_products/Classes/Utility/SerializationUtility.php @@ -0,0 +1,108 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Extbase\Persistence\ObjectStorage; +use TYPO3\CMS\Extbase\Service\ImageService; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +class SerializationUtility +{ + /** + * @var ImageService + */ + protected static $imageService; + + /** + * @var ContentObjectRenderer + */ + protected static $contentObject; + + /** + * @param $text + * @return string + */ + public static function sanitizeText($text) + { + $text = str_replace(' ', ' ', $text); + + return trim($text); + } + + /** + * @param ObjectStorage $images + * @param array $processingInstructions + * @return array + */ + public static function preprocessImages(ObjectStorage $images, array $processingInstructions = null) + { + $data = []; + foreach ($images as $file) + { + $image = $file->getOriginalResource(); + if (TYPO3_MODE === 'BE') { + $hostname = 'www.ep-reisen.de'; + } else { + $hostname = GeneralUtility::getIndpEnv('HTTP_HOST'); + } + if (!\is_array($processingInstructions)) { + $imageUrl = $image->getOriginalFile()->getPublicUrl(); + } else { + $imageService = static::getImageService(); + $cropString = $image->getProperty('crop'); + $cropVariantCollection = CropVariantCollection::create($cropString); + $cropVariant = $processingInstructions['cropVariant'] ?: 'default'; + $cropArea = $cropVariantCollection->getCropArea($cropVariant); + $processingInstructions['crop'] = $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image); + $processedImage = $imageService->applyProcessingInstructions($image, $processingInstructions); + $imageUrl = $processedImage->getPublicUrl(); + } + $data[] = sprintf('https://%s/%s', $hostname, $imageUrl); + } + + return $data; + } + + /** + * @return ImageService + */ + protected static function getImageService() + { + if (static::$imageService === null) { + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + static::$imageService = $objectManager->get(ImageService::class); + } + + return static::$imageService; + } + +} diff --git a/packages/ep_products/Classes/Utility/SettingsUtility.php b/packages/ep_products/Classes/Utility/SettingsUtility.php new file mode 100644 index 0000000..4e6fecd --- /dev/null +++ b/packages/ep_products/Classes/Utility/SettingsUtility.php @@ -0,0 +1,65 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManager; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; +use TYPO3\CMS\Extbase\Object\ObjectManager; + +class SettingsUtility +{ + /** + * @var array + */ + protected $settings = []; + + public function __construct() + { + /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $configurationManager = $objectManager->get(ConfigurationManager::class); + $settings = GeneralUtility::removeDotsFromTS( + $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + $this->settings = $settings['plugin']['tx_epproducts']['settings']; + } + + /** + * @param string $key + * @return string|array + */ + public function get($key = null) + { + if ($key !== null && array_key_exists($key, $this->settings)) { + return $this->settings[$key]; + } + + return $this->settings; + } +} diff --git a/packages/ep_products/Classes/Utility/TypeConversionUtility.php b/packages/ep_products/Classes/Utility/TypeConversionUtility.php new file mode 100644 index 0000000..2168d79 --- /dev/null +++ b/packages/ep_products/Classes/Utility/TypeConversionUtility.php @@ -0,0 +1,32 @@ + [ + 'class' => \EP\EpProducts\Command\ExportCommandController::class, + 'scheduleable' => false, + ], + 'ep:groups-swiss' => [ + 'class' => \EP\EpProducts\Command\GroupsSwissCommand::class, + ], + 'ep:contingent-import' => [ + 'class' => \EP\EpProducts\Command\ContingentCommand::class, + 'scheduleable' => false, + ], +]; diff --git a/packages/ep_products/Configuration/FlexForms/flexform_city_detail.xml b/packages/ep_products/Configuration/FlexForms/flexform_city_detail.xml new file mode 100644 index 0000000..0541cd5 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_city_detail.xml @@ -0,0 +1,40 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_city + AND tx_epproducts_domain_model_city.hidden = 0 AND tx_epproducts_domain_model_city.deleted = 0 ORDER BY tx_epproducts_domain_model_city.name + 1 + 1 + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_city_teaser.xml b/packages/ep_products/Configuration/FlexForms/flexform_city_teaser.xml new file mode 100644 index 0000000..a98c0d9 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_city_teaser.xml @@ -0,0 +1,79 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_country + ORDER BY name + + + + 0 + + + 0 + 1 + + + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_region + ORDER BY name + + + + 0 + + + 0 + 1 + + + + + + 0 + + + select + selectSingle + + + Winter + w + + + Sommer + s + + + 1 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_concept_detail.xml b/packages/ep_products/Configuration/FlexForms/flexform_concept_detail.xml new file mode 100644 index 0000000..cf5461c --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_concept_detail.xml @@ -0,0 +1,69 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_concept + AND tx_epproducts_domain_model_concept.hidden = 0 AND tx_epproducts_domain_model_concept.deleted = 0 ORDER BY tx_epproducts_domain_model_concept.name + + + Alle + 0 + + + 1 + 1 + + + + + + + + select + selectSingleBox + tx_epproducts_domain_model_concept + AND tx_epproducts_domain_model_concept.hidden = 0 AND tx_epproducts_domain_model_concept.deleted = 0 ORDER BY tx_epproducts_domain_model_concept.name + 5 + 0 + 10 + + + + + + 0 + + + check + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_country.xml b/packages/ep_products/Configuration/FlexForms/flexform_country.xml new file mode 100644 index 0000000..4b21262 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_country.xml @@ -0,0 +1,46 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_country + AND tx_epproducts_domain_model_country.hidden = 0 AND tx_epproducts_domain_model_country.deleted = 0 ORDER BY tx_epproducts_domain_model_country.name + + + + 0 + + + 0 + 1 + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_faq.xml b/packages/ep_products/Configuration/FlexForms/flexform_faq.xml new file mode 100644 index 0000000..cb9d809 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_faq.xml @@ -0,0 +1,32 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectMultipleSideBySide + tx_epproducts_domain_model_faq + AND tx_epproducts_domain_model_faq.hidden = 0 AND tx_epproducts_domain_model_faq.deleted = 0 ORDER BY tx_epproducts_domain_model_faq.label + 1 + 50 + 10 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail.xml b/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail.xml new file mode 100644 index 0000000..7dd6304 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail.xml @@ -0,0 +1,46 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_hotel + AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name + + + Alle + 0 + + + 0 + 1 + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail_event.xml b/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail_event.xml new file mode 100644 index 0000000..5a6878f --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail_event.xml @@ -0,0 +1,51 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_product + AND tx_epproducts_domain_model_product.hidden = 0 AND tx_epproducts_domain_model_product.deleted = 0 ORDER BY tx_epproducts_domain_model_product.name_internal + 1 + 1 + + + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_hotel + AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name + + + Alle + 0 + + + 1 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser.xml b/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser.xml new file mode 100644 index 0000000..62e436d --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser.xml @@ -0,0 +1,217 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + + + vierspaltig + 4 + + + dreispaltig + 3 + + + zweispaltig + 2 + + + 1 + 1 + + + + + + 0 + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_country + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + 0 + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_region + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + 0 + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_city + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_concept + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + reload + + select + selectSingle + tx_epproducts_domain_model_product + ORDER BY name_internal + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + FIELD:settings.productUid:REQ:TRUE + + select + selectSingle + + + Alle + 0 + + + Standard + 1 + + + Basic + 5 + + + Komfort + 2 + + + Deluxe + 3 + + + Superdeluxe + 4 + + + 0 + 1 + + + + + + + + select + selectCheckBox + + + Ferienwohnung + 1 + + + Sportclub + 2 + + + Apartment + 3 + + + Gästerhaus + 4 + + + Pension + 5 + + + 0 + 10 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser_date_independent.xml b/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser_date_independent.xml new file mode 100644 index 0000000..47dfae6 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser_date_independent.xml @@ -0,0 +1,176 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + + + vierspaltig + 4 + + + dreispaltig + 3 + + + zweispaltig + 2 + + + 1 + 1 + + + + + + 0 + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_country + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + 0 + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_region + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + reload + + select + selectSingle + tx_epproducts_domain_model_product + ORDER BY name_internal + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + FIELD:settings.productUid:REQ:TRUE + + select + selectSingle + + + Alle + 0 + + + Standard + 1 + + + Basic + 5 + + + Komfort + 2 + + + Deluxe + 3 + + + Superdeluxe + 4 + + + 0 + 1 + + + + + + + + select + selectCheckBox + + + Ferienwohnung + 1 + + + Sportclub + 2 + + + Apartment + 3 + + + Gästerhaus + 4 + + + Pension + 5 + + + 0 + 10 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_maps.xml b/packages/ep_products/Configuration/FlexForms/flexform_maps.xml new file mode 100644 index 0000000..c973c2e --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_maps.xml @@ -0,0 +1,47 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectMultipleSideBySide + tx_epproducts_domain_model_hotel + AND tx_epproducts_domain_model_hotel.latitude != "" AND tx_epproducts_domain_model_hotel.longitude != "" ORDER BY name + 0 + 50 + 10 + + + + + + 0 + + + select + selectMultipleSideBySide + tx_epproducts_domain_model_product + AND tx_epproducts_domain_model_product.detail_page != 0 ORDER BY name_internal + 0 + 50 + 10 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_pricetable.xml b/packages/ep_products/Configuration/FlexForms/flexform_pricetable.xml new file mode 100644 index 0000000..af6b5ae --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_pricetable.xml @@ -0,0 +1,65 @@ + + + 1 + + + + + + Konfiguration + + array + + + + + reload + + select + selectSingle + Pricetable->event + + + Preistabelle Eventreise + Pricetable->event + + + + + + + + + + check + 0 + + + + + + + + input + trim + + + + + + + + select + selectSingle + tx_epproducts_domain_model_product + AND tx_epproducts_domain_model_product.hidden = 0 AND tx_epproducts_domain_model_product.deleted = 0 ORDER BY tx_epproducts_domain_model_product.name_internal + 1 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_product_detail.xml b/packages/ep_products/Configuration/FlexForms/flexform_product_detail.xml new file mode 100644 index 0000000..4b1e2bf --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_product_detail.xml @@ -0,0 +1,75 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_product + AND tx_epproducts_domain_model_product.hidden = 0 AND tx_epproducts_domain_model_product.deleted = 0 ORDER BY tx_epproducts_domain_model_product.name_internal + + + Alle + 0 + + + 0 + 1 + + + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_hotel + AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name + + + Alle + 0 + + + 0 + 1 + + + + + + 0 + + + check + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_product_pricetable.xml b/packages/ep_products/Configuration/FlexForms/flexform_product_pricetable.xml new file mode 100644 index 0000000..5a6878f --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_product_pricetable.xml @@ -0,0 +1,51 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_product + AND tx_epproducts_domain_model_product.hidden = 0 AND tx_epproducts_domain_model_product.deleted = 0 ORDER BY tx_epproducts_domain_model_product.name_internal + 1 + 1 + + + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_hotel + AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name + + + Alle + 0 + + + 1 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_product_teaser.xml b/packages/ep_products/Configuration/FlexForms/flexform_product_teaser.xml new file mode 100644 index 0000000..9b9bfcb --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_product_teaser.xml @@ -0,0 +1,252 @@ + + + 1 + + + + + + Konfiguration + + array + + + + + reload + + select + selectSingle + Product->teasergroup + + + Teasergruppe + Product->teasergroup + + + Einzelteaser + Product->teaser + + + + + + + + + teasergroup]]> + + select + selectSingle + tx_epproducts_domain_model_country + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + teasergroup]]> + + select + selectSingle + tx_epproducts_domain_model_region + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + teasergroup]]> + + select + selectSingleBox + tx_epproducts_domain_model_concept + ORDER BY name + + + Keine Zuordnung + 0 + + + 5 + 0 + 10 + + + + + + + + select + selectSingle + tx_epproducts_domain_model_hotel + ORDER BY name + + + Alle + 0 + + + 0 + 1 + + + + + + + teasergroup]]> + + check + + + + + + + teasergroup]]> + + input + 10 + date + + + + + + + teasergroup]]> + + input + 10 + date + + + + + + + teaser]]> + + select + selectSingle + tx_epproducts_domain_model_product + ORDER BY name_internal + + + Alle + 0 + + + 0 + 1 + + + + + + + teasergroup]]> + + select + selectSingle + + + Spalten + cols + + + Reihen + rows + + + + + + + + + teasergroup]]> + + select + selectCheckBox + + + Ferienwohnung + 1 + + + Sportclub + 2 + + + Apartment + 3 + + + Gästehaus + 4 + + + Pension + 5 + + + 0 + 10 + + + + + + 0 + + + check + + + + + + + teasergroup]]> + + select + selectSingle + tx_epproducts_domain_model_infobox + ORDER BY title + + + Default + 0 + + + 0 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_region_detail.xml b/packages/ep_products/Configuration/FlexForms/flexform_region_detail.xml new file mode 100644 index 0000000..f0f3602 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_region_detail.xml @@ -0,0 +1,54 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_region + AND tx_epproducts_domain_model_region.hidden = 0 AND tx_epproducts_domain_model_region.deleted = 0 ORDER BY tx_epproducts_domain_model_region.name + 1 + 1 + + + + + + 0 + + + select + selectMultipleSideBySide + tx_epproducts_domain_model_region + AND tx_epproducts_domain_model_region.hidden = 0 AND tx_epproducts_domain_model_region.deleted = 0 ORDER BY tx_epproducts_domain_model_region.name + 0 + 5 + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_region_teaser.xml b/packages/ep_products/Configuration/FlexForms/flexform_region_teaser.xml new file mode 100644 index 0000000..69dd942 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_region_teaser.xml @@ -0,0 +1,53 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_country + ORDER BY name + 1 + 1 + + + + + + 0 + + + select + selectSingle + + + Winter + w + + + Sommer + s + + + 1 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_snowreport.xml b/packages/ep_products/Configuration/FlexForms/flexform_snowreport.xml new file mode 100644 index 0000000..6e865c8 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_snowreport.xml @@ -0,0 +1,32 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectMultipleSideBySide + tx_epproducts_domain_model_region + AND tx_epproducts_domain_model_region.snowreport > 0 ORDER BY name + 0 + 99 + 10 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_teammembers.xml b/packages/ep_products/Configuration/FlexForms/flexform_teammembers.xml new file mode 100644 index 0000000..cd68655 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_teammembers.xml @@ -0,0 +1,53 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + input + 20 + required + + + + + + 0 + + + input + 20 + required + + + + + + 0 + + + select + selectMultipleSideBySide + 1 + sys_category + 0 + 99 + + + + + + + + diff --git a/packages/ep_products/Configuration/RequestMiddlewares.php b/packages/ep_products/Configuration/RequestMiddlewares.php new file mode 100644 index 0000000..f8ec710 --- /dev/null +++ b/packages/ep_products/Configuration/RequestMiddlewares.php @@ -0,0 +1,39 @@ + [ + 'ep/staticsearch' => [ + 'target' => \EP\EpProducts\Middleware\StaticSearchParamsMiddleware::class, + 'before' => [ + 'typo3/cms-redirects/redirecthandler', + ], + 'after' => [ + 'typo3/cms-frontend/static-route-resolver', + ], + ], + 'ep/productapi' => [ + 'target' => \EP\EpProducts\Middleware\ApiMiddleware::class, + 'before' => [ + 'typo3/cms-frontend/page-resolver', + ], + 'after' => [ + 'typo3/cms-frontend/site', + ], + ], + 'ep/content-page-type-guard' => [ + 'target' => \EP\EpProducts\Middleware\ContentPageTypeGuardMiddleware::class, + 'after' => [ + 'typo3/cms-frontend/page-resolver', + ], + 'before' => [ + 'typo3/cms-frontend/page-argument-validator', + ], + ], + 'ep/staging-environment' => [ + 'target' => \EP\EpProducts\Middleware\StagingEnvironmentMiddleware::class, + 'after' => [ + 'typo3/cms-frontend/authentication', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/Services.yaml b/packages/ep_products/Configuration/Services.yaml new file mode 100644 index 0000000..2d88679 --- /dev/null +++ b/packages/ep_products/Configuration/Services.yaml @@ -0,0 +1,61 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + EP\EpProducts\: + resource: '../Classes/*' + exclude: '../Classes/Domain/Model/*' + + EP\EpProducts\EventListener\SystemInformationToolbarListener: + tags: + - name: event.listener + identifier: 'epToolbarListener' + event: TYPO3\CMS\Backend\Backend\Event\SystemInformationToolbarCollectorEvent + + cache.ep_cache: + class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface + public: true + factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache'] + arguments: ['ep_products_cache'] + + EP\EpProducts\Service\HotelImageService: + arguments: + $cache: '@cache.ep_cache' + + EP\EpProducts\Service\ProductImageService: + arguments: + $cache: '@cache.ep_cache' + + EP\EpProducts\Controller\AjaxCalendarController: + arguments: + $cache: '@cache.ep_cache' + + EP\EpProducts\Service\MyEpImageService: + arguments: + $cache: '@cache.ep_cache' + + ep_products.image_service.hotel: + class: EP\EpProducts\Service\MyEpImageService + arguments: + $cache: '@cache.ep_cache' + $table: 'tx_epproducts_domain_model_hotel' + + ep_products.image_service.product: + class: EP\EpProducts\Service\MyEpImageService + arguments: + $cache: '@cache.ep_cache' + $table: 'tx_epproducts_domain_model_product' + + ep_products.image_service.region: + class: EP\EpProducts\Service\MyEpImageService + arguments: + $cache: '@cache.ep_cache' + $table: 'tx_epproducts_domain_model_region' + + EP\EpProducts\Middleware\ApiMiddleware: + arguments: + $hotelImageService: '@ep_products.image_service.hotel' + $productImageService: '@ep_products.image_service.product' + $regionImageService: '@ep_products.image_service.region' diff --git a/packages/ep_products/Configuration/TCA/Overrides/sys_domain.php b/packages/ep_products/Configuration/TCA/Overrides/sys_domain.php new file mode 100644 index 0000000..ca778b0 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/Overrides/sys_domain.php @@ -0,0 +1,36 @@ + [ + 'label' => 'Vertragspartner', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_reseller', + 'foreign_table_where' => 'ORDER BY title', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + ]; + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns( + 'sys_domain', + $temporaryColumns + ); + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( + 'sys_domain', + 'tx_epproducts_reseller', + '', + 'before:redirectTo' + ); + +}); diff --git a/packages/ep_products/Configuration/TCA/Overrides/sys_template.php b/packages/ep_products/Configuration/TCA/Overrides/sys_template.php new file mode 100644 index 0000000..b0c3bc1 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/Overrides/sys_template.php @@ -0,0 +1,9 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_badge', + 'default_sortby' => 'ORDER BY title', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,subtitle', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, title, subtitle, link', + ], + ], + 'palettes' => [ + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_badge', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_badge.pid=###CURRENT_PID### AND tx_epproducts_domain_model_badge.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => 0, + 'label' => 'Titel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'subtitle' => [ + 'exclude' => 0, + 'label' => 'Untertitel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'link' => [ + 'exclude' => 0, + 'label' => 'Link', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_city.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_city.php new file mode 100644 index 0000000..2e2967a --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_city.php @@ -0,0 +1,470 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_city', + 'default_sortby' => 'ORDER BY name', + 'label' => 'name_internal', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,headline,teaser,description,party,leisure,teaser_images,header_images, images,latitude,longitude,country,region,facts,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, name_internal, --palette--;;name, --palette--;;destinations, teaser, + keywords, description, description_extended, party, leisure, facts, officetip, + --div--;Bilder/Dateien, teaser_images, header_images, images', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden,detail_page,exclude_from_teasergroups'], + 'name' => ['showitem' => 'name,headline,--linebreak--,header_title,header_subtitle'], + 'destinations' => ['showitem' => 'country,region,city,latitude,longitude,season'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_city', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_city.pid=###CURRENT_PID### AND tx_epproducts_domain_model_city.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'exclude_from_teasergroups' => [ + 'exclude' => 0, + 'label' => 'Nicht in Teasergruppen', + 'config' => [ + 'type' => 'check', + ], + ], + 'season' => [ + 'exclude' => 0, + 'label' => 'Saison', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Winter', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_WINTER], + ['Sommer', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_SUMMER], + ], + ] + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'name_internal' => [ + 'exclude' => 0, + 'label' => 'Name intern', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords für Suchschlitz', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Der Ort (oben)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description_extended' => [ + 'exclude' => 0, + 'label' => 'Der Ort (unten)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'party' => [ + 'exclude' => 0, + 'label' => 'Party', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'leisure' => [ + 'exclude' => 0, + 'label' => 'Freizeitangebot', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'Teaser', + 'value' => 2, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header', + 'value' => 3.8, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'latitude' => [ + 'exclude' => 0, + 'label' => 'Breitengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'longitude' => [ + 'exclude' => 0, + 'label' => 'Längengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'region' => [ + 'exclude' => 0, + 'label' => 'Gebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'officetip' => [ + 'exclude' => 0, + 'label' => 'Officetipp', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_officetip', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'facts' => [ + 'exclude' => 0, + 'label' => 'Facts', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'MM' => 'tx_epproducts_city_fact_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_fact', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_concept.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_concept.php new file mode 100644 index 0000000..f03196a --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_concept.php @@ -0,0 +1,415 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_concept', + 'default_sortby' => 'ORDER BY name', + 'sortby' => 'sorting', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,description,detail_page,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, --palette--;;naming, --palette--;;header, teaser, description, + description_extended, + --div--;Bilder/Dateien, teaser_images, header_images, images, + --div--;Popup, popup_text, --palette--;;popup', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden,detail_page,code'], + 'naming' => ['showitem' => 'name,headline'], + 'header' => ['showitem' => 'header_title,header_subtitle'], + 'popup' => ['showitem' => 'popup_title, popup_button_label'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_concept', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_concept.pid=###CURRENT_PID### AND tx_epproducts_domain_model_concept.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + + 'bus_pro_id' => [ + 'exclude' => 0, + 'label' => 'BusPro ID', + 'config' => [ + 'type' => 'none', + 'format' => 'int', + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 5, + 'eval' => 'trim,lower,required' + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Konzeptbeschreibung (oben)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description_extended' => [ + 'exclude' => 0, + 'label' => 'Konzeptbeschreibung (unten)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'popup_title' => [ + 'exclude' => 0, + 'label' => 'Popup Titel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => 'Hinweis', + ], + ], + 'popup_text' => [ + 'exclude' => 0, + 'label' => 'Popup Text', + 'description' => 'Wenn ausgefüllt, wird ein Popup mit diesem Text beim Anklicken eines Teasers angezeigt.', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 5, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'popup_button_label' => [ + 'exclude' => 0, + 'label' => 'Popup Button Label', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => 'Ok', + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaserEven' => [ + 'title' => 'Teaser gerade', + 'value' => 1.3333333333333334, + ], + 'teaserOdd' => [ + 'title' => 'Teaser ungerade', + 'value' => 2.0555555555555556, + ], + 'free' => [ + 'title' => 'Frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header groß', + 'value' => 2.3333333333333334, + ], + 'headerSmall' => [ + 'title' => 'Header klein', + 'value' => 3.3333333333333334, + ], + 'free' => [ + 'title' => 'Frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'products' => [ + 'exclude' => 0, + 'label' => 'Produkte', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_contingent.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_contingent.php new file mode 100644 index 0000000..2567d13 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_contingent.php @@ -0,0 +1,236 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_contingent', + 'label' => 'date', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getContingentLabel', + 'default_sortby' => 'ORDER BY date', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => false, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'room_code,room_label', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => 'date, pax, available, status, min_price, hotel, room_code, room_label, groupsch_id'], + ], + 'palettes' => [], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_contingent', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_contingent.pid=###CURRENT_PID### AND tx_epproducts_domain_model_contingent.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'pax' => [ + 'exclude' => 0, + 'label' => 'Pax', + 'config' => [ + 'type' => 'input', + 'size' => 3, + 'eval' => 'int,required' + ], + ], + 'available' => [ + 'exclude' => 0, + 'label' => 'verfügbar', + 'config' => [ + 'type' => 'input', + 'size' => 3, + 'eval' => 'int,required' + ], + ], + 'date' => [ + 'exclude' => 0, + 'label' => 'Datum', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date,required', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ], + ], + 'hotel' => [ + 'exclude' => 0, + 'label' => 'Unterkunft', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'status' => [ + 'exclude' => 0, + 'label' => 'Status', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'minitems' => 1, + 'maxitems' => 1, + 'items' => [ + [ 'Ok', \EP\EpProducts\Domain\Model\Contingent::STATUS_OK ], + [ 'Buchungsstop', \EP\EpProducts\Domain\Model\Contingent::STATUS_BLOCKED ], + [ 'auf Anfrage', \EP\EpProducts\Domain\Model\Contingent::STATUS_ONREQUEST ], + ] + ], + ], + 'min_price' => [ + 'exclude' => 0, + 'label' => 'ab Preis', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'min_nights' => [ + 'exclude' => 0, + 'label' => 'ab Preis Nächte', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'additional_night_min_price' => [ + 'exclude' => 0, + 'label' => 'ab Preis Verlängerungsnacht', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'additional_night_min_nights' => [ + 'exclude' => 0, + 'label' => 'Verlängerungsnacht min. Nächte', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'groupsch_id' => [ + 'exclude' => 0, + 'label' => 'groups.ch ID', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'trim,int' + ], + ], + 'room_code' => [ + 'exclude' => 0, + 'label' => 'Zimmercode', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'trim' + ], + ], + 'room_label' => [ + 'exclude' => 0, + 'label' => 'Zimmerbezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_country.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_country.php new file mode 100644 index 0000000..e947bab --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_country.php @@ -0,0 +1,444 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_country', + 'label' => 'name_internal', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name_internal', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'code,name,headline,teaser,description,teaser_images,images,latitude,longitude,facts,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => ' + --palette--;;top, name_internal, --palette--;;name, headline, --palette--;;geolocation, keywords, + --div--;Texte, teaser, description, description_extended, + --div--;Verknüpfungen, facts, snowreport, officetip, + --div--;Bilder/Dateien, teaser_images, header_images, images', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden,detail_page'], + 'name' => ['showitem' => 'code,name,--linebreak--,header_title,header_subtitle'], + 'geolocation' => ['showitem' => 'latitude,longitude,season'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_country.pid=###CURRENT_PID### AND tx_epproducts_domain_model_country.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 2, + 'eval' => 'trim,required' + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'name_internal' => [ + 'exclude' => 0, + 'label' => 'Name intern', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'season' => [ + 'exclude' => 0, + 'label' => 'Saison', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Winter', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_WINTER], + ['Sommer', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_SUMMER], + ], + ] + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords für Suchschlitz', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Beschreibung (oben)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description_extended' => [ + 'exclude' => 0, + 'label' => 'Beschreibung (unten)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'Teaser', + 'value' => 2, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header', + 'value' => 3.8, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'latitude' => [ + 'exclude' => 0, + 'label' => 'Breitengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'longitude' => [ + 'exclude' => 0, + 'label' => 'Längengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'officetip' => [ + 'exclude' => 0, + 'label' => 'Officetipp', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_officetip', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'snowreport' => [ + 'exclude' => 0, + 'label' => 'Gebiet für Schneehöhen', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'items' => [ + ['Keine Zuordnung', 0] + ], + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_snowreport', + 'foreign_table_where' => 'ORDER BY tx_epproducts_domain_model_snowreport.region_name', + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'facts' => [ + 'exclude' => 0, + 'label' => 'Facts', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'MM' => 'tx_epproducts_country_fact_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_fact', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_date.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_date.php new file mode 100644 index 0000000..473e602 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_date.php @@ -0,0 +1,446 @@ + [ + 'title' => 'Termin', + 'label' => 'bus_pro_id', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getDateLabel', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => false, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'bus_pro_id,code,date_start,date_end,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => 'hidden, bus_pro_id, hotel_bus_pro_id, code, daytrip, date_start, date_end, nights, board, + board_code, board_bus_pro_id, bus_included, bus_available, skipass_included, group_travel, + services_included, services_general, courses_included, guide, pickups, discount, min_price, pseudo_price, available, min_pax, + new' + ], + ], + 'palettes' => [ + '1' => ['showitem' => ''], + ], + 'columns' => [ + 'sys_language_uid' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_date', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_date.pid=###CURRENT_PID### AND tx_epproducts_domain_model_date.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'product' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hotel' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'country' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'region' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'city' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + // Denormalized columns written by DateImportService, which rebuilds this table + // wholesale. They are TEXT NOT NULL, and MySQL cannot default that, so without a + // TCA entry DataHandler omits them and every INSERT fails under STRICT_TRANS_TABLES. + 'product_keywords' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'product_teaser' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'concept_popup_text' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'country_keywords' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'region_keywords' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'city_keywords' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hotel_keywords' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hotel_teaser' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'bus_pro_id' => [ + 'exclude' => false, + 'label' => 'BusPro ID', + 'config' => [ + 'type' => 'none', + 'format' => 'int', + ], + ], + 'hotel_bus_pro_id' => [ + 'exclude' => false, + 'label' => 'Hotel BusPro ID', + 'config' => [ + 'type' => 'none', + 'format' => 'int', + ], + ], + 'code' => [ + 'exclude' => false, + 'label' => 'Kürzel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'daytrip' => [ + 'exclude' => false, + 'label' => 'tageweise buchbar', + 'config' => [ + 'type' => 'check', + 'default' => 0 + ] + ], + 'season' => [ + 'exclude' => false, + 'label' => 'Saison', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + [ 'Winter', 'w' ], + [ 'Sommer', 's' ], + ], + 'default' => 'w', + ], + ], + 'date_start' => [ + 'exclude' => false, + 'label' => 'Datum von', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ], + ], + 'date_end' => [ + 'exclude' => false, + 'label' => 'Datum bis', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ], + ], + 'nights' => [ + 'exclude' => false, + 'label' => 'Nächte', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'board' => [ + 'exclude' => false, + 'label' => 'Verpflegung', + 'config' => [ + 'type' => 'none', + ], + ], + 'board_code' => [ + 'exclude' => false, + 'label' => 'Verpflegung Code', + 'config' => [ + 'type' => 'none', + ], + ], + 'board_bus_pro_id' => [ + 'exclude' => false, + 'label' => 'Verpflegung BusPro ID', + 'config' => [ + 'type' => 'none', + 'format' => 'int', + ], + ], + 'bus_included' => [ + 'exclude' => false, + 'label' => 'Bus inklusive', + 'config' => [ + 'type' => 'check', + 'default' => 0 + ] + ], + 'bus_available' => [ + 'exclude' => false, + 'label' => 'Bus verfügbar', + 'config' => [ + 'type' => 'check', + 'default' => 0 + ] + ], + 'skipass_included' => [ + 'exclude' => false, + 'label' => 'Skipass inklusive', + 'config' => [ + 'type' => 'check', + 'default' => 0 + ] + ], + 'group_travel' => [ + 'exclude' => false, + 'label' => 'Gruppenreise', + 'config' => [ + 'type' => 'check', + 'default' => 0 + ] + ], + 'bus_price' => [ + 'exclude' => false, + 'label' => 'Bus Preis', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'services_included' => [ + 'exclude' => false, + 'label' => 'Inklusivleistungen', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'services_general' => [ + 'exclude' => false, + 'label' => 'Leistungen', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'courses_included' => [ + 'exclude' => false, + 'label' => 'Kurse', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'guide' => [ + 'exclude' => false, + 'label' => 'Reiseleiter', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'pickups' => [ + 'exclude' => false, + 'label' => 'Zustiege', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'discount' => [ + 'exclude' => false, + 'label' => 'Rabatt', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'min_price' => [ + 'exclude' => false, + 'label' => 'ab Preis', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'pseudo_price' => [ + 'exclude' => false, + 'label' => 'Pseudopreis', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'available' => [ + 'exclude' => false, + 'label' => 'verfügbar', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'min_pax' => [ + 'exclude' => false, + 'label' => 'Min. Pax', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'new' => [ + 'exclude' => false, + 'label' => 'neu', + 'config' => [ + 'type' => 'check', + 'default' => 0, + ], + ], + ] +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_fact.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_fact.php new file mode 100644 index 0000000..16716e1 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_fact.php @@ -0,0 +1,145 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_fact', + 'label' => 'label', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY label', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,label,keywords', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => 'hidden, label, name'], + ], + 'palettes' => [ + '1' => ['showitem' => ''], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_fact.pid=###CURRENT_PID### AND tx_epproducts_domain_model_fact.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'label' => [ + 'exclude' => 0, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords für Suchschlitz', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_faq.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_faq.php new file mode 100644 index 0000000..a72ded0 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_faq.php @@ -0,0 +1,170 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_faq', + 'label' => 'label', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'question,answer,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, category, label, question, answer', + ], + ], + 'palettes' => [ + '1' => ['showitem' => ''], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_faq', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_faq.pid=###CURRENT_PID### AND tx_epproducts_domain_model_faq.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'category' => [ + 'exclude' => 0, + 'label' => 'Kategorie', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + [ '', '' ], + [ 'Buchung & Reiseablauf', 'Buchung & Reiseablauf' ], + [ 'Skipass, Skigebiet & Kurse', 'Skipass, Skigebiet & Kurse' ], + [ 'Änderungen & Zusatzleistungen', 'Änderungen & Zusatzleistungen' ], + [ 'Anreise & Transport', 'Anreise & Transport' ], + [ 'Unterkunft & Sportclubs', 'Unterkunft & Sportclubs' ], + [ 'Programm & Aktivitäten', 'Programm & Aktivitäten' ], + [ 'Eventreisen', 'Eventreisen' ], + [ 'Teilnahmebedingungen', 'Teilnahmebedingungen' ], + [ 'Zahlung & Rechnung', 'Zahlung & Rechnung' ], + [ 'Kontakt & Erreichbarkeit', 'Kontakt & Erreichbarkeit' ], + [ 'Stornierung, Reklamation & Versicherung', 'Stornierung, Reklamation & Versicherung' ], + ], + 'default' => '', + ], + ], + 'label' => [ + 'exclude' => 0, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'question' => [ + 'exclude' => 0, + 'label' => 'Frage', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'answer' => [ + 'exclude' => 0, + 'label' => 'Antwort', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim,required', + 'enableRichtext' => true, + ] + ], + + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php new file mode 100644 index 0000000..a2acf0e --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php @@ -0,0 +1,151 @@ + [ + 'title' => 'Verpflegungsleistung', + 'default_sortby' => 'ORDER BY price', + 'sortby' => 'sorting', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + 'hideTable' => true, + ], + 'types' => [ + '1' => [ + 'showitem' => 'title, price, price_chf', + ], + ], + 'palettes' => [ + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => -1, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceboard', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_groupspriceboard.pid=###CURRENT_PID### AND tx_epproducts_domain_model_groupspriceboard.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 32, + 'eval' => 'required', + ] + ], + 'price' => [ + 'exclude' => false, + 'label' => 'Preis pro Person/Nacht', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2,required', + ] + ], + 'price_chf' => [ + 'exclude' => false, + 'label' => 'Preis pro Person/Nacht in CHF', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2', + ] + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceconfig.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceconfig.php new file mode 100644 index 0000000..1be71f2 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceconfig.php @@ -0,0 +1,186 @@ + [ + 'title' => 'Gruppenhaus Preis', + 'default_sortby' => 'ORDER BY date_from', + 'label' => 'date_from', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getGroupsPriceConfigLabel', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'date_from', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + 'hideTable' => true, + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;dates, --palette--;;included, --palette--;;prices, --palette--;;prices_groups', + ], + ], + 'palettes' => [ + 'dates' => ['showitem' => 'date_from, date_to'], + 'included' => ['showitem' => 'persons_included'], + 'prices' => ['showitem' => 'price, price_chf, price_additional_person, price_additional_person_chf'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => -1, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceconfig', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_groupspriceconfig.pid=###CURRENT_PID### AND tx_epproducts_domain_model_groupspriceconfig.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'date_from' => [ + 'exclude' => false, + 'label' => 'Datum von', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'date,required', + 'renderType' => 'inputDateTime', + ] + ], + 'date_to' => [ + 'exclude' => false, + 'label' => 'Datum bis', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'date,required', + 'renderType' => 'inputDateTime', + ] + ], + 'persons_included' => [ + 'exclude' => false, + 'label' => 'Inklusiv-Personen', + 'config' => [ + 'type' => 'input', + 'size' => 4, + 'eval' => 'int,required', + ] + ], + 'price' => [ + 'exclude' => false, + 'label' => 'Preis pro Nacht', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2,required', + ] + ], + 'price_chf' => [ + 'exclude' => false, + 'label' => 'Preis pro Nacht in CHF', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2', + ] + ], + 'price_additional_person' => [ + 'exclude' => false, + 'label' => 'Preis weitere Person', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2', + ] + ], + 'price_additional_person_chf' => [ + 'exclude' => false, + 'label' => 'Preis weitere Person in CHF', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2', + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php new file mode 100644 index 0000000..ffe085d --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php @@ -0,0 +1,175 @@ + [ + 'title' => 'Gruppenhaus Zusatzleistung', + 'default_sortby' => 'ORDER BY price', + 'sortby' => 'sorting', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + 'hideTable' => true, + ], + 'types' => [ + '1' => [ + 'showitem' => 'title, --palette--;;priceconfig', + ], + ], + 'palettes' => [ + 'priceconfig' => ['showitem' => 'price, price_chf, type'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => -1, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceoption', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_groupspriceoption.pid=###CURRENT_PID### AND tx_epproducts_domain_model_groupspriceoption.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 32, + 'eval' => 'required', + ] + ], + 'price' => [ + 'exclude' => false, + 'label' => 'Preis', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2,required', + ] + ], + 'price_chf' => [ + 'exclude' => false, + 'label' => 'Preis in CHF', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2', + ] + ], + 'type' => [ + 'exclude' => false, + 'label' => 'Typ', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['pauschal', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_GLOBAL], + ['pro Person', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_PER_PAX], + ['pro Nacht', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_PER_NIGHT], + ['pro Person und Nacht', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_PER_PAX_AND_NIGHT], + ], + 'default' => \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_GLOBAL, + ] + ], + 'ignore_with_board' => [ + 'exclude' => false, + 'label' => 'Entfällt bei Verpflegung', + 'config' => [ + 'type' => 'check', + 'default' => 0, + ] + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php new file mode 100644 index 0000000..bdf1d97 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php @@ -0,0 +1,850 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_hotel', + 'default_sortby' => 'ORDER BY name', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getHotelLabel', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,headline,teaser,description,features,room_types,additional_information,teaser_images, + header_images,images,reseller_images,code,type,category,country,region,city,teamer,products,external_link', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, --palette--;;name, --palette--;;geolocation, + --palette--;;destinations, --palette--;;categories, keywords, + --div--;Texte, teaser, description, features, room_types, additional_information, + --div--;Verknüpfungen, facts, teamer, additional_codes, mappings, + --div--;Bilder/Dateien, teaser_images, header_images, images, reseller_images, + --div--;Gruppenhaus Icons, --palette--;;icons', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden,earlybird,new,low_contingent,show_as_teaser, + --linebreak--,code,detail_page,--linebreak--,external_link,path_segment'], + 'geolocation' => ['showitem' => 'address, longitude, latitude'], + 'destinations' => ['showitem' => 'country,region,city'], + 'name' => ['showitem' => 'name,short_name,--linebreak--,header_title,header_subtitle,--linebreak--,headline'], + 'categories' => ['showitem' => 'type,category'], + 'season' => ['showitem' => 'main_season_from, main_season_to'], + 'others' => ['showitem' => 'season,groups_price_tax_label, groups_price_adolescents_age'], + 'icons' => ['showitem' => 'beds_count,parking_count,altitude,shower,partyroom,gamesroom,sauna,wifi,distance_bus,distance_lift'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_hotel.pid=###CURRENT_PID### AND tx_epproducts_domain_model_hotel.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'new' => [ + 'exclude' => 0, + 'label' => 'Neu', + 'config' => [ + 'type' => 'check', + ], + ], + 'earlybird' => [ + 'exclude' => 0, + 'label' => 'Frühbucher', + 'config' => [ + 'type' => 'check', + ], + ], + 'low_contingent' => [ + 'exclude' => 0, + 'label' => 'wenige Betten', + 'config' => [ + 'type' => 'check', + ], + ], + 'show_as_teaser' => [ + 'exclude' => 0, + 'label' => 'als Teaser anzeigen', + 'config' => [ + 'type' => 'check', + ], + ], + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'text', + 'cols' => 30, + 'rows' => 2, + 'eval' => 'trim,required' + ], + ], + 'short_name' => [ + 'exclude' => 0, + 'label' => 'Kurzname', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords für Suchschlitz', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Die Unterkunft', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'features' => [ + 'exclude' => 0, + 'label' => 'Ausstattung', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'room_types' => [ + 'exclude' => 0, + 'label' => 'Zimmerarten/Apartmentbeschreibung', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'additional_information' => [ + 'exclude' => 0, + 'label' => 'Zusatzinformationen', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'Teaser', + 'value' => 2, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header', + 'value' => 3.8, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'reseller_images' => [ + 'exclude' => 0, + 'label' => 'Bilder für Vertriebspartner', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'reseller_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'external_link' => [ + 'exclude' => 0, + 'label' => 'Externer Link (Google MyBusiness)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'renderType' => 'inputLink', + ], + ], + 'type' => [ + 'exclude' => 0, + 'label' => 'Typ', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Unbestimmt', 0], + ['Ferienwohnung', \EP\EpProducts\Domain\Model\Hotel::TYPE_HOLIDAY_FLAT], + ['Sportclub', \EP\EpProducts\Domain\Model\Hotel::TYPE_SPORTS_CLUB], + ['Apartment', \EP\EpProducts\Domain\Model\Hotel::TYPE_APPARTMENT], + ['Gästehaus', \EP\EpProducts\Domain\Model\Hotel::TYPE_GUESTHOUSE], + ['Pension', \EP\EpProducts\Domain\Model\Hotel::TYPE_PENSION], + ], + 'size' => 1, + 'minItems' => 1, + 'maxItems' => 1 + ], + ], + 'category' => [ + 'exclude' => 0, + 'label' => 'Kategorie', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Unbestimmt', 0], + ['Standard', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_STANDARD], + ['Basic', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_BASIC], + ['Komfort', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_COMFORT], + ['Deluxe', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_DELUXE], + ['Super Deluxe', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_SUPER_DELUXE], + ], + 'size' => 1, + 'minItems' => 1, + 'maxItems' => 1 + ] + ], + 'country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'region' => [ + 'exclude' => 0, + 'label' => 'Gebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'city' => [ + 'exclude' => 0, + 'label' => 'Ort', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_city', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'facts' => [ + 'exclude' => 0, + 'label' => 'Facts', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'MM' => 'tx_epproducts_hotel_fact_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_fact', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + 'products' => [ + 'exclude' => 0, + 'label' => 'Produkte', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'MM' => 'tx_epproducts_product_hotel_mm', + 'MM_opposite_field' => 'hotels', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + ], + ], + 'teamer' => [ + 'exclude' => 0, + 'label' => 'Teamer', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_teammember', + 'MM' => 'tx_epproducts_hotel_teammember_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 1, + ], + ], + 'additional_codes' => [ + 'exclude' => 0, + 'label' => 'Zusätzliche Codes', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_matchcode', + 'foreign_field' => 'entity', + 'foreign_match_fields' => [ + 'entity_type' => 'hotel', + ], + 'maxitems' => 10, + 'appearance' => [ + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0, + 'expandSingle' => 1, + ], + ], + ], + 'mappings' => [ + 'exclude' => 0, + 'label' => 'abweichende Detailseiten', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_hotel_mapping', + 'foreign_field' => 'hotel', + 'maxitems' => 10, + 'appearance' => [ + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0, + 'expandSingle' => 1, + ], + ], + ], + 'latitude' => [ + 'exclude' => 0, + 'label' => 'Breitengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'longitude' => [ + 'exclude' => 0, + 'label' => 'Längengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'address' => [ + 'exclude' => 0, + 'label' => 'Anschrift', + 'config' => [ + 'type' => 'text', + 'cols' => 30, + 'rows' => 5, + 'eval' => 'trim', + ], + ], + 'path_segment' => [ + 'exclude' => 0, + 'label' => 'URL Segment', + 'config' => [ + 'type' => 'slug', + 'generatorOptions' => [ + 'fields' => ['name'], + 'prefixParentPageSlug' => false, + 'replacements' => [ + '/' => '-', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + ], + ], + 'groups_price_configs' => [ + 'exclude' => 0, + 'label' => 'Preiskonfigurationen', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceconfig', + 'foreign_field' => 'hotel', + 'maxitems' => 99, + 'minitems' => 0, + 'appearance' => [ + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0, + 'expandSingle' => 1, + ], + ], + ], + 'groups_price_boards' => [ + 'exclude' => 0, + 'label' => 'Verpflegungsleistungen', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceboard', + 'foreign_field' => 'hotel', + 'foreign_sortby' => 'sorting', + 'maxitems' => 99, + 'minitems' => 0, + 'appearance' => [ + 'useSortable' => true, + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0, + 'expandSingle' => 1, + ], + ], + ], + 'groups_price_options' => [ + 'exclude' => 0, + 'label' => 'Optionale Zusatzleistungen', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceoption', + 'foreign_field' => 'hotel', + 'foreign_sortby' => 'sorting', + 'maxitems' => 99, + 'minitems' => 0, + 'appearance' => [ + 'useSortable' => true, + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0, + 'expandSingle' => 1, + ], + ], + ], + 'groups_price_tax_label' => [ + 'exclude' => 0, + 'label' => 'Text Ortstaxe', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + 'default' => 'zzgl. vorab zu entrichtender Ortstaxe', + ], + ], + 'groups_price_adolescents_age' => [ + 'exclude' => 0, + 'label' => 'Altersgrenze Jugendliche (6-X Jahre)', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'int', + ], + ], + 'main_season_from' => [ + 'exclude' => false, + 'label' => 'Hauptsaison von', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ], + ], + 'main_season_to' => [ + 'exclude' => false, + 'label' => 'Hauptsaison bis', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ], + ], + 'beds_count' => [ + 'exclude' => 0, + 'label' => 'Anzahl Betten', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'int', + ], + ], + 'parking_count' => [ + 'exclude' => 0, + 'label' => 'Anzahl Parkplätze', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'int', + ], + ], + 'altitude' => [ + 'exclude' => 0, + 'label' => 'Höhe', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + ], + ], + 'shower' => [ + 'exclude' => 0, + 'label' => 'Dusche', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + ], + ], + 'partyroom' => [ + 'exclude' => 0, + 'label' => 'Partyraum', + 'config' => [ + 'type' => 'check', + ], + ], + 'gamesroom' => [ + 'exclude' => 0, + 'label' => 'Spieleraum', + 'config' => [ + 'type' => 'check', + ], + ], + 'sauna' => [ + 'exclude' => 0, + 'label' => 'Sauna', + 'config' => [ + 'type' => 'check', + ], + ], + 'wifi' => [ + 'exclude' => 0, + 'label' => 'WiFi', + 'config' => [ + 'type' => 'check', + ], + ], + 'distance_bus' => [ + 'exclude' => 0, + 'label' => 'Entfernung zum Bus', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + ], + ], + 'distance_lift' => [ + 'exclude' => 0, + 'label' => 'Entfernung zum Lift', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel_mapping.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel_mapping.php new file mode 100644 index 0000000..3e2726c --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel_mapping.php @@ -0,0 +1,141 @@ + [ + 'title' => 'Hotelmapping', + 'label' => 'product', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'product', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + 'hideTable' => true, + ], + 'types' => [ + '1' => [ + 'showitem' => 'product,detail_page', + ], + ], + 'palettes' => [], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_hotel_mapping', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_hotel_mapping.pid=###CURRENT_PID### AND tx_epproducts_domain_model_hotel_mapping.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'product' => [ + 'exclude' => 0, + 'label' => 'Produkt', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'minitems' => 1, + 'maxitems' => 1, + 'multiple' => 0, + ], + ], + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'hotel' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_infobox.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_infobox.php new file mode 100644 index 0000000..3317085 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_infobox.php @@ -0,0 +1,145 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_infobox', + 'default_sortby' => 'ORDER BY title', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,text', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, title, text', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden, default_text'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_infobox', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_infobox.pid=###CURRENT_PID### AND tx_epproducts_domain_model_infobox.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => 0, + 'label' => 'Titel (nur intern)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'text' => [ + 'exclude' => 0, + 'label' => 'Text', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim,required', + 'enableRichtext' => true, + ] + ], + 'default_text' => [ + 'exclude' => 0, + 'label' => 'Default', + 'config' => [ + 'type' => 'check', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_journey.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_journey.php new file mode 100644 index 0000000..5413972 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_journey.php @@ -0,0 +1,170 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_journey', + 'default_sortby' => 'ORDER BY name', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,by_car,by_bus,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, name, by_car, by_bus, image', + ], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_journey', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_journey.pid=###CURRENT_PID### AND tx_epproducts_domain_model_journey.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'by_car' => [ + 'exclude' => 0, + 'label' => 'PKW/Eigenanreise', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'by_bus' => [ + 'exclude' => 0, + 'label' => 'Busanreise', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'image' => [ + 'exclude' => 0, + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 0, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_matchcode.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_matchcode.php new file mode 100644 index 0000000..cf92888 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_matchcode.php @@ -0,0 +1,139 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_matchcode', + 'label' => 'code', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => false, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'code', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => 'code'], + ], + 'palettes' => [ + '1' => ['showitem' => ''], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_matchcode', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_matchcode.pid=###CURRENT_PID### AND tx_epproducts_domain_model_matchcode.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'entity_type' => [ + 'exclude' => 0, + 'label' => 'Entität', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + [ + 'Hotel', + 'hotel', + ], + ], + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_officetip.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_officetip.php new file mode 100644 index 0000000..0bc185b --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_officetip.php @@ -0,0 +1,147 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_officetip', + 'label' => 'label', + 'prependAtCopy' => '[COPY %s]', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getOfficetipLabel', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'label,text,author', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => '--palette--;;top, text'], + ], + 'palettes' => [ + 'top' => ['showitem' => 'label,author,hidden'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_officetip', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_officetip.pid=###CURRENT_PID### AND tx_epproducts_domain_model_officetip.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'label' => [ + 'exclude' => 0, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'text' => [ + 'exclude' => 0, + 'label' => 'Text', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim,required' + ] + ], + 'author' => [ + 'exclude' => 0, + 'label' => 'Autor', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_teammember', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_product.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_product.php new file mode 100644 index 0000000..a7cd7b2 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_product.php @@ -0,0 +1,951 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_product', + 'label' => 'name_internal', + 'prependAtCopy' => '[COPY %s]', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getProductLabel', + 'default_sortby' => 'ORDER BY name_internal', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,headline', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, name_internal, --palette--;;name, --palette--;;header, + --palette--;;destinations, --palette--;;misc, --palette--;Alternative;alternative, + --div--;Texte, teaser, teaser_long, concept_description, board_description, additional_services, + included_services, programme_description, ski_pass_alt_title, ski_pass_description, + non_bookable_info, + --div--;Verknüpfungen, --palette--;;records, hotels, facts, faqs, additional_regions, + --div--;Bilder/Dateien, teaser_images, header_images, images, reseller_images, banner, video, + tour_thumbnail, + --div--;Bewertung, --palette--;;rating, testimonials, + --div--;Buchbarkeit, non_bookable_dates', + ], + ], + 'palettes' => [ + 'destinations' => ['showitem' => 'country,region,city'], + 'top' => ['showitem' => 'searchable, bookable, hide_booking_button, daytrip, important, skipass_included, + season, detail_page, external_link, tour_link, path_segment, min_price'], + 'name' => ['showitem' => 'name,code'], + 'alternative' => ['showitem' => 'alt_product, alt_label'], + 'header' => ['showitem' => 'headline, subline, --linebreak--, header_title, header_subtitle'], + 'rating' => ['showitem' => 'rating_average, rating_votes_count'], + 'records' => ['showitem' => 'journey, concept, calendar_hotel, officetip, badge'], + 'misc' => ['showitem' => 'feature,keywords'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_product.pid=###CURRENT_PID### AND tx_epproducts_domain_model_product.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_product.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'searchable' => [ + 'exclude' => 0, + 'label' => 'erscheint im Suchergebnis', + 'config' => [ + 'type' => 'check', + ], + ], + 'bookable' => [ + 'exclude' => 0, + 'label' => 'online buchbar', + 'config' => [ + 'type' => 'check', + ], + ], + 'hide_booking_button' => [ + 'exclude' => 0, + 'label' => 'Buchungsbutton verstecken', + 'config' => [ + 'type' => 'check', + ], + ], + 'daytrip' => [ + 'exclude' => 0, + 'label' => 'tageweise buchbar', + 'config' => [ + 'type' => 'check', + ], + ], + 'important' => [ + 'exclude' => 0, + 'label' => 'in Teasergruppen immer vorne', + 'config' => [ + 'type' => 'check', + ], + ], + 'skipass_included' => [ + 'exclude' => 0, + 'label' => 'Skipass inklusive', + 'config' => [ + 'type' => 'check', + 'default' => true, + ], + ], + 'season' => [ + 'exclude' => 0, + 'label' => 'Saison', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + [ 'Winter', 'w' ], + [ 'Sommer', 's' ], + ], + 'default' => 'w', + ], + ], + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'ID Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + ] + ], + 'bus_pro_id' => [ + 'exclude' => 0, + 'label' => 'BusProId', + 'config' => [ + 'type' => 'none', + 'format' => 'int', + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'text', + 'cols' => 30, + 'rows' => 2, + 'eval' => 'trim,required' + ], + ], + 'name_internal' => [ + 'exclude' => 0, + 'label' => 'Name intern', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'subline' => [ + 'exclude' => 0, + 'label' => 'Subline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords', + 'description' => 'für Suchschlitz (veraltet)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 2, + 'eval' => 'trim', + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'feature' => [ + 'exclude' => 0, + 'label' => 'Leistungen', + 'description' => 'für Teaser, max 2', + 'config' => [ + 'type' => 'text', + 'cols' => 30, + 'rows' => 2, + 'eval' => 'trim' + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser (Aufzählung)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'teaser_long' => [ + 'exclude' => 0, + 'label' => 'Teaser (Fließtext)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'concept_description' => [ + 'exclude' => 0, + 'label' => 'Konzeptbeschreibung', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'board_description' => [ + 'exclude' => 0, + 'label' => 'Verpflegung', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'additional_services' => [ + 'exclude' => 0, + 'label' => 'Zusatzleistungen', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'included_services' => [ + 'exclude' => 0, + 'label' => 'Inklusivleistungen (Gruppen)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 5, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'programme_description' => [ + 'exclude' => 0, + 'label' => 'Programm', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'ski_pass_alt_title' => [ + 'exclude' => 0, + 'label' => 'Skipass alternative überschrift', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'ski_pass_description' => [ + 'exclude' => 0, + 'label' => 'Skipass', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'rating_average' => [ + 'exclude' => 0, + 'label' => 'Durchschnittliche Bewertung', + 'config' => [ + 'type' => 'input', + 'size' => 5, + 'eval' => 'double2' + ], + ], + 'rating_votes_count' => [ + 'exclude' => 0, + 'label' => 'Abgebene Stimmen', + 'config' => [ + 'type' => 'input', + 'size' => 10, + 'eval' => 'int' + ], + ], + 'testimonials' => [ + 'exclude' => 0, + 'label' => 'Testimonials', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 5, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'Teaser', + 'value' => 2, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header', + 'value' => 3.805, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Slider', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.625, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'reseller_images' => [ + 'exclude' => 0, + 'label' => 'Bilder für Vertriebspartner', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'reseller_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'facebook' => [ + 'title' => 'Facebook', + 'allowedAspectRatios' => [ + 'ad' => [ + 'title' => 'Single product ad', + 'value' => 1.91, + ], + 'carousel' => [ + 'title' => 'Carousel', + 'value' => 1, + ], + ] + ], + 'google' => [ + 'title' => 'Google', + 'allowedAspectRatios' => [ + 'catalog' => [ + 'title' => 'Catalog', + 'value' => 1, + ], + ] + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'tour_thumbnail' => [ + 'exclude' => 0, + 'label' => 'Thumbnail Rundgang', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'tour_thumbnail', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 1, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'concept' => [ + 'exclude' => 0, + 'label' => 'Konzept', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_concept', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minItems' => 1, + 'maxitems' => 1, + ], + ], + 'country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'region' => [ + 'exclude' => 0, + 'label' => 'Gebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'city' => [ + 'exclude' => 0, + 'label' => 'Stadt/Ort', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_city', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'journey' => [ + 'exclude' => 0, + 'label' => 'Anreise', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_journey', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'items' => [ + ['Keine Zuordnung', 0] + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'facts' => [ + 'exclude' => 0, + 'label' => 'Facts', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_fact.name', + 'MM' => 'tx_epproducts_product_fact_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_fact', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + 'hotels' => [ + 'exclude' => 0, + 'label' => 'Unterkünfte', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_hotel.name', + 'MM' => 'tx_epproducts_product_hotel_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'minItems' => 1, + 'maxitems' => 9999, + 'multiple' => 0, + ], + ], + 'calendar_hotel' => [ + 'exclude' => 0, + 'label' => 'Unterkunft für Belegungskalender', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_hotel.name', + 'items' => [ + [ 'Kein Kalender', 0 ], + ], + ], + ], + 'officetip' => [ + 'exclude' => 0, + 'label' => 'Officetipp', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_officetip', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'faqs' => [ + 'exclude' => 0, + 'label' => 'FAQs', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_faq', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_faq.label', + 'MM' => 'tx_epproducts_product_faq_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_faq', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + 'alt_product' => [ + 'exclude' => 0, + 'label' => 'Produkt', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_product.uid != ###THIS_UID### ORDER BY tx_epproducts_domain_model_product.name_internal', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'alt_label' => [ + 'exclude' => 0, + 'label' => 'Button Label', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'video' => [ + 'exclude' => 0, + 'label' => 'Video (Vimeo ID)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'banner' => [ + 'exclude' => 0, + 'label' => 'Banner für Sidebar', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'banner', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'additional_regions' => [ + 'exclude' => 0, + 'label' => 'Weitere Gebiete', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_region.deleted = 0 ORDER BY tx_epproducts_domain_model_region.name', + 'MM' => 'tx_epproducts_product_region_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + ], + ], + 'badge' => [ + 'exclude' => 0, + 'label' => 'Badge', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_badge', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_badge.deleted = 0 ORDER BY tx_epproducts_domain_model_badge.title', + 'items' => [ + ['Keine Auswahl', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'non_bookable_info' => [ + 'exclude' => 0, + 'label' => 'Infobox nicht buchbar', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_infobox', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_infobox.deleted = 0 ORDER BY tx_epproducts_domain_model_infobox.title', + 'items' => [ + ['Standardtext', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'external_link' => [ + 'exclude' => false, + 'label' => 'Externer Link', + 'config' => [ + 'type' => 'input', + 'eval' => 'trim', + ] + ], + 'tour_link' => [ + 'exclude' => 0, + 'label' => 'URL 360° Rundgang (matterport)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'renderType' => 'inputLink', + ], + ], + 'path_segment' => [ + 'exclude' => 0, + 'label' => 'Buchungslink URL', + 'config' => [ + 'type' => 'slug', + 'generatorOptions' => [ + 'fields' => ['name'], + 'prefixParentPageSlug' => false, + 'replacements' => [ + '/' => '-', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + ], + ], + 'non_bookable_dates' => [ + 'exclude' => 0, + 'label' => 'Nicht buchbare Termine', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'itemsProcFunc' => \EP\EpProducts\UserFunc\TcaProcFunc::class.'->getDateItemsForProduct', + ] + ], + 'min_price' => [ + 'exclude' => 0, + 'label' => 'Ab-Preis', + 'config' => [ + 'type' => 'input', + 'eval' => 'int' + ], + ] + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_region.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_region.php new file mode 100644 index 0000000..b2c7cf6 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_region.php @@ -0,0 +1,609 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_region', + 'label' => 'name_internal', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name_internal', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,headline,teaser,ski_area,ski_pass,description,news', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, name_internal, --palette--;;name, --palette--;;destinations, + --palette--;;livedata, teaser, keywords, facts, officetip,--palette--;Auszeichnung;award, + --div--;Texte, ski_area, ski_area_extended, ski_pass, description, news, + --div--;Daten, --palette--;;dimensions, + --div--;Bilder/Dateien, teaser_images, header_images, images, region_maps', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden,detail_page,exclude_from_teasergroups'], + 'name' => ['showitem' => 'name,headline,--linebreak--,header_title,header_subtitle'], + 'destinations' => ['showitem' => 'country,season,latitude,longitude'], + 'dimensions' => ['showitem' => 'length,height,lifts'], + 'livedata' => ['showitem' => 'snowreport,webcam,--linebreak--,feature'], + 'award' => ['showitem' => 'award_link, award_label, --linebreak--, award_image'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_region.pid=###CURRENT_PID### AND tx_epproducts_domain_model_region.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'exclude_from_teasergroups' => [ + 'exclude' => 0, + 'label' => 'Nicht in Teasergruppen', + 'config' => [ + 'type' => 'check', + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'name_internal' => [ + 'exclude' => 0, + 'label' => 'Name intern', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'season' => [ + 'exclude' => 0, + 'label' => 'Saison', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Winter', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_WINTER], + ['Sommer', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_SUMMER], + ], + ] + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords für Suchschlitz', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'ski_area' => [ + 'exclude' => 0, + 'label' => 'Das Skigebiet/Highlights (oben)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'ski_area_extended' => [ + 'exclude' => 0, + 'label' => 'Das Skigebiet/Highlights (unten)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'ski_pass' => [ + 'exclude' => 0, + 'label' => 'Der Skipass', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Anfänger, Snowpark, Freeriden', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'news' => [ + 'exclude' => 0, + 'label' => 'Aktuell', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'length' => [ + 'exclude' => 0, + 'label' => 'Pisten', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'height' => [ + 'exclude' => 0, + 'label' => 'Höhe', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'lifts' => [ + 'exclude' => 0, + 'label' => 'Lifte', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'feature' => [ + 'exclude' => 0, + 'label' => 'Feature für Teaser', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'Teaser', + 'value' => 2, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header', + 'value' => 3.8, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'region_maps' => [ + 'exclude' => 0, + 'label' => 'Gebietskarten', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'region_maps', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 5 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'latitude' => [ + 'exclude' => 0, + 'label' => 'Breitengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'longitude' => [ + 'exclude' => 0, + 'label' => 'Längengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'facts' => [ + 'exclude' => 0, + 'label' => 'Facts', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'MM' => 'tx_epproducts_region_fact_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_fact', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + 'officetip' => [ + 'exclude' => 0, + 'label' => 'Officetip', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_officetip', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'snowreport' => [ + 'exclude' => 0, + 'label' => 'Gebiet für Schneehöhen', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['Keine Zuordnung', 0] + ], + 'foreign_table' => 'tx_epproducts_domain_model_snowreport', + 'foreign_table_where' => 'ORDER BY tx_epproducts_domain_model_snowreport.region_name', + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'webcam' => [ + 'exclude' => 0, + 'label' => 'Webcam', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'items' => [ + ['Keine Zuordnung', 0] + ], + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_webcam', +// 'foreign_table_where' => 'AND tx_epproducts_domain_model_webcam.available = 1 AND tx_epproducts_domain_model_webcam.snowreport=###REC_FIELD_snowreport### ORDER BY tx_epproducts_domain_model_webcam.name', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_webcam.snowreport=###REC_FIELD_snowreport### ORDER BY tx_epproducts_domain_model_webcam.name', + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'award_image' => [ + 'exclude' => 0, + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'award_image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'award_link' => [ + 'exclude' => 0, + 'label' => 'Link', + 'config' => [ + 'type' => 'input', + 'size' => 32, + 'eval' => 'trim', + 'renderType' => 'inputLink', + ] + ], + 'award_label' => [ + 'exclude' => 0, + 'label' => 'Label', + 'config' => [ + 'type' => 'input', + 'size' => 32, + 'eval' => 'trim', + 'default' => 'Auszeichnung Skigebiet', + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_reseller.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_reseller.php new file mode 100644 index 0000000..eae2dfe --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_reseller.php @@ -0,0 +1,154 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_reseller', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => 'name, code, pa_code, products'], + ], + 'palettes' => [], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_reseller', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_reseller.pid=###CURRENT_PID### AND tx_epproducts_domain_model_reseller.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'pa_code' => [ + 'exclude' => 0, + 'label' => 'PA-Code', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'products' => [ + 'exclude' => 0, + 'label' => 'Produkte', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'MM' => 'tx_epproducts_reseller_product_mm', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_product.name_internal', + 'minitems' => 1, + 'maxitems' => 99, + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_snowreport.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_snowreport.php new file mode 100644 index 0000000..3a09753 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_snowreport.php @@ -0,0 +1,286 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_snowreport', + 'label' => 'region_name', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY region_name', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'region_name', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, vendor_uid, region_name, glacier, height_max, height_min, opened, + snow_level_mountain, snow_level_valley, snow_quality, last_snowfall, lifts_total, lifts_opened, + last_update, season_start, season_end, --div--;Webcams, webcams', + ], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_snowreport', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_snowreport.pid=###CURRENT_PID### AND tx_epproducts_domain_model_snowreport.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'pid' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'vendor_uid' => [ + 'exclude' => 0, + 'label' => 'Schneereport ID', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'region_name' => [ + 'exclude' => 0, + 'label' => 'Skigebiet', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'glacier' => [ + 'exclude' => 0, + 'label' => 'Gletscher', + 'config' => [ + 'type' => 'check', + ], + ], + 'height_max' => [ + 'exclude' => 0, + 'label' => 'Höhe max.', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'height_min' => [ + 'exclude' => 0, + 'label' => 'Höhe min.', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'opened' => [ + 'exclude' => 0, + 'label' => 'Gebiet geöffnet', + 'config' => [ + 'type' => 'check', + ], + ], + 'snow_level_mountain' => [ + 'exclude' => 0, + 'label' => 'Schneehöhe Berg', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'snow_level_valley' => [ + 'exclude' => 0, + 'label' => 'Schneehöhe Tal', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'snow_quality' => [ + 'exclude' => 0, + 'label' => 'Schneequalität', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'last_snowfall' => [ + 'exclude' => 0, + 'label' => 'Letzter Schneefall', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'range' => [ + 'lower' => mktime(0, 0, 0, date('m'), date('d'), date('Y')) + ], + 'renderType' => 'inputDateTime', + ], + ], + 'lifts_total' => [ + 'exclude' => 0, + 'label' => 'Lifte gesamt', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'lifts_opened' => [ + 'exclude' => 0, + 'label' => 'Lifte geöffnet', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'last_update' => [ + 'exclude' => 0, + 'label' => 'Letzte Aktualisierung', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'range' => [ + 'lower' => mktime(0, 0, 0, date('m'), date('d'), date('Y')) + ], + 'renderType' => 'inputDateTime', + ], + ], + 'webcams' => [ + 'exclude' => 0, + 'label' => 'Webcams', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_webcam', + 'foreign_field' => 'snowreport', + ], + ], + 'season_start' => [ + 'exclude' => 0, + 'label' => 'Saison Anfang', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'range' => [ + 'lower' => mktime(0, 0, 0, date('m'), date('d'), date('Y')) + ], + 'renderType' => 'inputDateTime', + ], + ], + 'season_end' => [ + 'exclude' => 0, + 'label' => 'Saison Ende', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'range' => [ + 'lower' => mktime(0, 0, 0, date('m'), date('d'), date('Y')) + ], + 'renderType' => 'inputDateTime', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_staticsearchparams.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_staticsearchparams.php new file mode 100644 index 0000000..cb1801c --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_staticsearchparams.php @@ -0,0 +1,258 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_staticsearchparams', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,path_segment', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'name, path_segment, country, region, concept, hotel, product, date_from, + date_to, hotel_types, bus, pax', + ], + ], + 'palettes' => [], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_staticsearchparams', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_staticsearchparams.pid=###CURRENT_PID### AND tx_epproducts_domain_model_staticsearchparams.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_product.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'bus' => [ + 'exclude' => 0, + 'label' => 'Bus', + 'config' => [ + 'type' => 'check', + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'cols' => 30, + 'eval' => 'trim,required' + ], + ], + 'path_segment' => [ + 'exclude' => 0, + 'label' => 'URL Segment', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required,unique,alphanum_x,lower' + ], + ], + 'concept' => [ + 'exclude' => 0, + 'label' => 'Konzept', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingleBox', + 'foreign_table' => 'tx_epproducts_domain_model_concept', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minItems' => 0, + 'maxitems' => 10, + 'size' => 5, + 'items' => [ + [ 'Keine Auswahl', 0 ], + ] + ], + ], + 'country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minitems' => 0, + 'maxitems' => 1, + 'items' => [ + [ 'Keine Auswahl', 0 ], + ] + ], + ], + 'region' => [ + 'exclude' => 0, + 'label' => 'Gebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minitems' => 0, + 'maxitems' => 1, + 'items' => [ + [ 'Keine Auswahl', 0 ], + ] + ], + ], + 'hotel' => [ + 'exclude' => 0, + 'label' => 'Hotel', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_hotel.name', + 'minitems' => 0, + 'maxitems' => 1, + 'items' => [ + [ 'Keine Auswahl', 0 ], + ] + ], + ], + 'hotel_types' => [ + 'exclude' => 0, + 'label' => 'Unterkunftsarten', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectCheckBox', + 'items' => [ + [ 'Keine Auswahl', 0 ], + [ 'Standard', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_STANDARD ], + [ 'Komfort', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_COMFORT ], + [ 'Deluxe', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_DELUXE ], + [ 'Superdeluxe', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_SUPER_DELUXE ], + ], + 'minitems' => 0, + 'maxitems' => 99, + ] + ], + 'date_from' => [ + 'exclude' => 0, + 'label' => 'Datum von', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ] + ], + 'date_to' => [ + 'exclude' => 0, + 'label' => 'Datum bis', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ] + ], + 'pax' => [ + 'exclude' => 0, + 'label' => 'Personen', + 'config' => [ + 'type' => 'input', + 'size' => 2, + 'eval' => 'int', + ] + ] + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_teammember.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_teammember.php new file mode 100644 index 0000000..16e9fa4 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_teammember.php @@ -0,0 +1,214 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_teammember', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name', + 'sortby' => 'sorting', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,email,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, --palette--;;top, --palette--;;contact, description, image, path_segment', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'name,tasks'], + 'contact' => ['showitem' => 'email,phone'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_teammember', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_teammember.pid=###CURRENT_PID### AND tx_epproducts_domain_model_teammember.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'path_segment' => [ + 'exclude' => 0, + 'label' => 'Pfadsegment', + 'config' => [ + 'type' => 'slug', + 'generatorOptions' => [ + 'fields' => ['name'], + 'prefixParentPageSlug' => false, + 'replacements' => [ + '/' => '-', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + ], + ], + 'email' => [ + 'exclude' => 0, + 'label' => 'E-Mail', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'phone' => [ + 'exclude' => 0, + 'label' => 'Telefon', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'tasks' => [ + 'exclude' => 0, + 'label' => 'Aufgaben', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 2, + 'eval' => 'trim' + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Beschreibung', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'image' => [ + 'exclude' => 0, + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php new file mode 100644 index 0000000..a65bd53 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php @@ -0,0 +1,374 @@ + [ + 'title' => 'Reiseinformationen', + 'default_sortby' => 'ORDER BY title', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'origUid' => 't3_origuid', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,travel_code', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;basics,--div--;Texte,--palette--;;text,--div--;Bilder,--palette--;;images', + ], + ], + 'palettes' => [ + 'basics' => ['showitem' => 'title,path_segment,--linebreak--,travel_code,hide_address,--linebreak--,links, + --linebreak--,teasers'], + 'text' => ['showitem' => 'subline,headline,--linebreak--,intro_text,--linebreak--, bus_info_text,--linebreak--, + departure_text,--linebreak--,footer_text,--linebreak--,self_arranged_text,--linebreak--,additional_info, + --linebreak--,important_phone_numbers,--linebreak--,important_links'], + 'images' => ['showitem' => 'header_image,--linebreak--,footer_image'], + ], + 'columns' => [ + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_travelinfo', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_travelinfo.pid=###CURRENT_PID### AND tx_epproducts_domain_model_travelinfo.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'Titel (nur intern genutzt)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'subline' => [ + 'exclude' => 0, + 'label' => 'Subline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_image' => [ + 'exclude' => 0, + 'label' => 'Headerbild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'footer_image' => [ + 'exclude' => 0, + 'label' => 'Footerbild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'footer_image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'travel_code' => [ + 'exclude' => false, + 'label' => 'Reisecode', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'hide_address' => [ + 'exclude' => 0, + 'label' => 'Adresse ausblenden', + 'config' => [ + 'type' => 'check', + ], + ], + 'intro_text' => [ + 'exclude' => false, + 'label' => 'Introtext', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim,required', + 'enableRichtext' => true, + ] + ], + 'bus_info_text' => [ + 'exclude' => false, + 'label' => 'Businfo', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'departure_text' => [ + 'exclude' => false, + 'label' => 'Abreise', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'footer_text' => [ + 'exclude' => false, + 'label' => 'Footertext', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim,required', + 'enableRichtext' => true, + ] + ], + 'self_arranged_text' => [ + 'exclude' => false, + 'label' => 'Eigenanreise', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'additional_info' => [ + 'exclude' => false, + 'label' => 'Weitere Infos', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'important_phone_numbers' => [ + 'exclude' => false, + 'label' => 'Wichtige Telefonnummern', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'important_links' => [ + 'exclude' => false, + 'label' => 'Wichtige Links', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'teasers' => [ + 'exclude' => false, + 'label' => 'Inhalte', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_travelinfoteaser', + 'foreign_sortby' => 'sorting', + 'foreign_field' => 'tx_epproducts_travelinfo', + 'minitems' => 0, + 'maxitems' => 99, + 'appearance' => [ + 'collapseAll' => true, + 'expandSingle' => true, + 'levelLinksPosition' => 'bottom', + 'useSortable' => true, + 'showPossibleLocalizationRecords' => false, + 'showRemovedLocalizationRecords' => false, + 'showAllLocalizationLink' => false, + 'showSynchronizationLink' => false, + 'enabledControls' => [ + 'info' => false, + ], + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => false, + ], + ] + ], + 'links' => [ + 'exclude' => false, + 'label' => 'Links', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_travelinfolink', + 'foreign_sortby' => 'sorting', + 'foreign_field' => 'tx_epproducts_travelinfo', + 'minitems' => 0, + 'maxitems' => 3, + 'appearance' => [ + 'collapseAll' => true, + 'expandSingle' => true, + 'levelLinksPosition' => 'bottom', + 'useSortable' => true, + 'showPossibleLocalizationRecords' => false, + 'showRemovedLocalizationRecords' => false, + 'showAllLocalizationLink' => false, + 'showSynchronizationLink' => false, + 'enabledControls' => [ + 'info' => false, + ], + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => false, + ], + ] + ], + 'path_segment' => [ + 'exclude' => 0, + 'label' => 'URL', + 'config' => [ + 'type' => 'slug', + 'generatorOptions' => [ + 'fields' => ['title'], + 'prefixParentPageSlug' => false, + 'replacements' => [ + '/' => '-', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfolink.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfolink.php new file mode 100644 index 0000000..552ed74 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfolink.php @@ -0,0 +1,167 @@ + [ + 'title' => 'Reiseinfo Link', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;basics,link', + ], + ], + 'palettes' => [ + 'basics' => ['showitem' => 'title,icon,hidden'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_travelinfolink', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_travelinfolink.pid=###CURRENT_PID### AND tx_epproducts_domain_model_travelinfolink.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'link' => [ + 'exclude' => false, + 'label' => 'Link', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputLink', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'icon' => [ + 'exclude' => 0, + 'label' => 'Icon', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Anreise', 'travel'], + ['Skipass', 'skipass'], + ['Kurse', 'course'], + ['Verpflegung', 'food'], + ['Gepäck', 'luggage'], + ['Lift', 'lift'], + ['Webcam', 'webcam'], + ['Wetter', 'weather'], + ['Wissenswertes', 'chat'], + ['Währung', 'currency'], + ], + ], + ], + 'tx_epproducts_travelinfo' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfoteaser.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfoteaser.php new file mode 100644 index 0000000..d22d0d7 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfoteaser.php @@ -0,0 +1,220 @@ + [ + 'title' => 'Reiseinfo Teaser', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'origUid' => 't3_origuid', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;basics,bodytext,image', + ], + ], + 'palettes' => [ + 'basics' => ['showitem' => 'title,category,hidden'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_travelinfoteaser', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_travelinfoteaser.pid=###CURRENT_PID### AND tx_epproducts_domain_model_travelinfoteaser.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'Bezeichnung/Überschrift', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'bodytext' => [ + 'exclude' => false, + 'label' => 'Text', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim,required', + 'enableRichtext' => true, + ] + ], + 'image' => [ + 'exclude' => 0, + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'quadtratisch', + 'value' => 1, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'category' => [ + 'exclude' => 0, + 'label' => 'Kategorie/Icon', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Anreise', 'travel'], + ['Auto', 'car'], + ['Bus', 'bus'], + ['Gepäck', 'luggage'], + ['Getränke', 'drinks'], + ['Kurse', 'course'], + ['Lift', 'lift'], + ['Programm', 'program'], + ['Skipass', 'skipass'], + ['Verpflegung', 'food'], + ['Webcam', 'webcam'], + ['Wetter', 'weather'], + ['Wissenswertes', 'chat'], + ['Leihmaterial', 'rental'], + ['Teilnehmer', 'participants'], + ['Bett', 'bed'], + ['Ablauf', 'sequence'], + ['Partner', 'partner'], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + ], + 'tx_epproducts_travelinfo' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_webcam.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_webcam.php new file mode 100644 index 0000000..777bb74 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_webcam.php @@ -0,0 +1,164 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_webcam', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, name, url, vendor_uid, available, snowreport', + ], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_webcam', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_webcam.pid=###CURRENT_PID### AND tx_epproducts_domain_model_webcam.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'pid' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'url' => [ + 'exclude' => 0, + 'label' => 'URL', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'vendor_uid' => [ + 'exclude' => 0, + 'label' => 'ID', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'available' => [ + 'exclude' => 0, + 'label' => 'verfügbar', + 'config' => [ + 'type' => 'check', + ], + ], + 'snowreport' => [ + 'exclude' => 0, + 'label' => 'Skigebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingleBox', + 'foreign_table' => 'tx_epproducts_domain_model_snowreport', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TypoScript/constants.typoscript b/packages/ep_products/Configuration/TypoScript/constants.typoscript new file mode 100644 index 0000000..6d6c778 --- /dev/null +++ b/packages/ep_products/Configuration/TypoScript/constants.typoscript @@ -0,0 +1,68 @@ +# customsubcategory=100=Storage +# customsubcategory=110=Pages +# customsubcategory=120=Search/Filtering +# customsubcategory=130=Email +# customsubcategory=140=Misc + +plugin.tx_epproducts { + settings { + # cat=epproducts/100/100; type=string; label=Products storage PID + productsStoragePid = + # cat=epproducts/100/110; type=string; label=Hotels storage PID + hotelsStoragePid = + # cat=epproducts/100/120; type=string; label=Concepts storage PID + conceptsStoragePid = + # cat=epproducts/100/130; type=string; label=Facts storage PID + factsStoragePid = + # cat=epproducts/100/140; type=string; label=Faqs storage PID + faqsStoragePid = + # cat=epproducts/100/150; type=string; label=Destinations storage PID + destinationsStoragePid = + # cat=epproducts/100/160; type=string; label=Regions storage PID + regionsStoragePid = + # cat=epproducts/100/160; type=string; label=Cities storage PID + citiesStoragePid = + # cat=epproducts/100/170; type=string; label=Teammember storage PID + teammemberStoragePid = + # cat=epproducts/100/180; type=string; label=Officetip storage PID + officetipStoragePid = + # cat=epproducts/100/190; type=string; label=Snow report storage PID + snowReportStoragePid = + # cat=epproducts/110/100; type=string; label=Default search page UID + defaultSearchPageUid = + # cat=epproducts/110/110; type=string; label=Default product detail page UID + defaultDetailPageUid = + # cat=epproducts/110/120; type=string; label=Page UID with reseller export plugin + resellerExportPageUid = + # cat=epproducts/110/130; type=string; label=Page UID with travel info plugin + travelInfoPageUid = + # cat=epproducts/120/100; type=string; label=Exclude products with concept UIDs from search result (CSV) + excludedConceptUids = + # cat=epproducts/120/110; type=string; label=Exclude regions with these UIDs from filter options (CSV) + excludedRegionUids = + # cat=epproducts/120/120; type=string; label=Force concept UID in all search related parts (CSV) + forcedConceptUids = + # cat=epproducts/120/130; type=string; label=Don't show availability info for products with these concept UIDs + noInfoConceptUids = + # cat=epproducts/130/110; type=string; label=Email recipient for groups price inquiries + groupsPriceToEmail = gruppen@ep-reisen.de + # cat=epproducts/130/120; type=string; label=Email recipient for groups price inquiries + groupsPriceToName = EP Reisen + # cat=epproducts/130/130; type=string; label=Email recipient for groups price inquiries + groupsBookingUrl = https://myep.ddev.site/groups/booking/init + # cat=epproducts/140/100; type=string; label=Room types for pricetables (CSV) + priceTableRoomTypes = 1,2,3,4,5,6,7,8,9,10 + # cat=epproducts/140/110; type=string; label=Ignore following IPs in searchlog + searchLogIgnoreIps = + # cat=epproducts/140/120; type=string; label=Concept UIDs to trigger inquiry form on product detail page (CSV) + groupInquiryFormConceptUids = + # cat=epproducts/140/130; type=string; label=Per-site MyE&P booking URL override + myEpUrl = + # cat=epproducts/140/140; type=string; label=Per-site MyE&P staging booking URL override + myEpStagingUrl = + } + persistence { + # cat=epproducts/100/100; type=string; label=Default storage PID + storagePid = + } +} diff --git a/packages/ep_products/Configuration/TypoScript/setup.typoscript b/packages/ep_products/Configuration/TypoScript/setup.typoscript new file mode 100644 index 0000000..a5378bb --- /dev/null +++ b/packages/ep_products/Configuration/TypoScript/setup.typoscript @@ -0,0 +1,303 @@ +plugin.tx_epproducts { + view { + layoutRootPaths.0 = EXT:ep_theme/Resources/Private/Layouts/Product/ + templateRootPaths.0 = EXT:ep_theme/Resources/Private/Templates/ + partialRootPaths.0 = EXT:ep_theme/Resources/Private/Partials/ + } + persistence { + storagePid = {$plugin.tx_epproducts.persistence.storagePid} + classes { + EP\EpProducts\Domain\Model\Product.newRecordStoragePid = {$plugin.tx_epproducts.settings.productsStoragePid} + EP\EpProducts\Domain\Model\Hotel.newRecordStoragePid = {$plugin.tx_epproducts.settings.hotelsStoragePid} + EP\EpProducts\Domain\Model\Country.newRecordStoragePid = {$plugin.tx_epproducts.settings.destinationsStoragePid} + EP\EpProducts\Domain\Model\Region.newRecordStoragePid = {$plugin.tx_epproducts.settings.destinationsStoragePid} + EP\EpProducts\Domain\Model\City.newRecordStoragePid = {$plugin.tx_epproducts.settings.destinationsStoragePid} + EP\EpProducts\Domain\Model\Journey.newRecordStoragePid = {$plugin.tx_epproducts.settings.destinationsStoragePid} + EP\EpProducts\Domain\Model\Concept.newRecordStoragePid = {$plugin.tx_epproducts.settings.conceptsStoragePid} + EP\EpProducts\Domain\Model\Fact.newRecordStoragePid = {$plugin.tx_epproducts.settings.factsStoragePid} + EP\EpProducts\Domain\Model\Faq.newRecordStoragePid = {$plugin.tx_epproducts.settings.faqsStoragePid} + EP\EpProducts\Domain\Model\Teammember.newRecordStoragePid = {$plugin.tx_epproducts.settings.teammemberStoragePid} + EP\EpProducts\Domain\Model\Officetip.newRecordStoragePid = {$plugin.tx_epproducts.settings.officetipStoragePid} + EP\EpProducts\Domain\Model\Room.newRecordStoragePid = {$plugin.tx_epproducts.persistence.storagePid} + EP\EpProducts\Domain\Model\Date.newRecordStoragePid = {$plugin.tx_epproducts.persistence.storagePid} + EP\EpProducts\Domain\Model\MatchCode.newRecordStoragePid = {$plugin.tx_epproducts.persistence.storagePid} + } + } + settings { + productsPid = {$plugin.tx_epproducts.settings.productsStoragePid} + hotelsPid = {$plugin.tx_epproducts.settings.hotelsStoragePid} + destinationsStoragePid = {$plugin.tx_epproducts.settings.destinationsStoragePid} + regionsStoragePid = {$plugin.tx_epproducts.settings.regionsStoragePid} + citiesStoragePid = {$plugin.tx_epproducts.settings.citiesStoragePid} + snowReportStoragePid = {$plugin.tx_epproducts.settings.snowReportStoragePid} + defaultSearchPageUid = {$plugin.tx_epproducts.settings.defaultSearchPageUid} + defaultDetailPageUid = {$plugin.tx_epproducts.settings.defaultDetailPageUid} + excludedConceptUids = {$plugin.tx_epproducts.settings.excludedConceptUids} + excludedRegionUids = {$plugin.tx_epproducts.settings.excludedRegionUids} + forcedConceptUids = {$plugin.tx_epproducts.settings.forcedConceptUids} + noInfoConceptUids = {$plugin.tx_epproducts.settings.noInfoConceptUids} + priceTableRoomTypes = {$plugin.tx_epproducts.settings.priceTableRoomTypes} + searchLogIgnoreIps = {$plugin.tx_epproducts.settings.searchLogIgnoreIps} + myEpUrl = {$plugin.tx_epproducts.settings.myEpUrl} + myEpStagingUrl = {$plugin.tx_epproducts.settings.myEpStagingUrl} + groupsPageUid = {$plugin.tx_eptheme.settings.groupsPageUid} + travelInfoPageUid = {$plugin.tx_eptheme.settings.travelInfoPageUid} + groupsContactFormPageUid = {$plugin.tx_eptheme.settings.groupsContactFormPageUid} + coronaUpdatesPageUid = {$plugin.tx_eptheme.settings.coronaUpdatesPageUid} + googleMapsApiKey = {$plugin.tx_eptheme.settings.googleMapsApiKey} + resellerExportPageUid = {$plugin.tx_epproducts.settings.resellerExportPageUid} + ratingPageUid = {$plugin.tx_eptheme.settings.ratingPageUid} + travelAlertPageUid = {$plugin.tx_eptheme.settings.travelAlertPageUid} + searchPageHeaderImage = {$plugin.tx_eptheme.settings.searchPageHeaderImage} + bpnBookingUrlTemplateCode = {$plugin.tx_eptheme.settings.bpnBookingUrlTemplateCode} + groupsPricePopupPageUid = {$plugin.tx_eptheme.settings.groupsPricePopupPageUid} + groupsPriceToEmail = {$plugin.tx_epproducts.settings.groupsPriceToEmail} + groupsPriceToName = {$plugin.tx_epproducts.settings.groupsPriceToName} + groupInquiryFormConceptUids = {$plugin.tx_epproducts.settings.groupInquiryFormConceptUids} + groupsBookingUrl = {$plugin.tx_epproducts.settings.groupsBookingUrl} + logoImage = {$plugin.tx_eptheme.settings.logoImage} + themekey = {$plugin.tx_eptheme.settings.themekey} + globalConceptCode = {$plugin.tx_eptheme.settings.globalConceptCode} + dataProtectionPageUid = {$plugin.tx_eptheme.settings.dataProtectionPageUid} + datePickerPresets { + 1 { + label = Silvester + dateFrom = 2017-12-25 + dateTo = 2018-01-06 + } + 2 { + label = Karneval + dateFrom = 2018-02-03 + dateTo = 2018-02-17 + } + 3 { + label = Ostern + dateFrom = 2018-03-24 + dateTo = 2018-04-05 + } + } + groupsSwissApiKeys { + # Spinabad + 7 { + hotelCode = DGS + number = 9849 + key = a8b504ab2784446cbf73b8b1f6777f43 + } + # Waldschlössli + 6 { + hotelCode = DPW + number = 9658 + key = f757b34dc3ac4a158257cf12c9ce69e1 + } + # Jenatsch + 37 { + hotelCode = LPJNEU + number = 9703 + key = 52c314a88a434b359717115db992d20d + } + # Schweizerhaus + 5 { + hotelCode = DKS + number = 9850 + key = 4a955b23158d40c583b2892a18aa1043 + } + # Klein Tirol + 65 { + hotelCode = MVK + number = 668904 + key = e9d86dbc465848d7a0b68df14e3e482f + } + # Weissfluh + 131 { + hotelCode = DWW21/22 + number = 961523 + key = 6a22585d23184f2090bd7110aa9b60f3 + } + } + } +} + +config.tx_extbase.persistence.classes { + EP\EpProducts\Domain\Model\Log.mapping { + tableName = tx_epproducts_log + columns { + data.mapOnProperty = data + time_micro.mapOnProperty = timeMicro + } + } +} + +module.tx_epproducts { + settings < plugin.tx_epproducts.settings + persistence < plugin.tx_epproducts.persistence +} + +# Register Ajax page types (json and html output) +tx_epproducts_ajax_json = PAGE +tx_epproducts_ajax_json { + typeNum = 1701 + config { + disableAllHeaderCode = 1 + disablePrefixComment = 1 + additionalHeaders.10.header = Content-type: application/json + additionalHeaders.20.header = X-Robots-Tag: noindex, nofollow, noarchive + xhtml_cleaning = 0 + admPanel = 0 + debug = 0 + + # The calendar actions are cacheable, but they render volatile availability data. + # TSFE would otherwise fall back to cacheTimeOutDefault (86400), which would widen + # staleness from one hour to a full day. Keep this aligned with the freshness budget. + cache_period = 3600 + # priceCalendarAction renders relative to 'today', so an entry must not outlive the + # day it was rendered on. Near midnight this clips the lifetime below cache_period. + cache_clearAtMidnight = 1 + + # The server-side page cache can be flushed; a max-age already sitting in a visitor's + # browser cannot. Availability is volatile enough that we keep the speed win on the + # server and make clients revalidate every time - they still hit a warm page cache. + # sendCacheHeaders is off so TSFE does not also emit a contradicting Expires/Pragma. + sendCacheHeaders = 0 + additionalHeaders.30.header = Cache-Control: private, no-cache, must-revalidate + + # Same reasoning: staticfilecache serves from disk without touching PHP, which is the + # layer with the least predictable invalidation. Not worth it for volatile fragments. + tx_staticfilecache.disableCache = 1 + } + + 10 = USER + 10 { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = ajax + vendorName = EP + controller = AjaxSearch + action = searchresult + switchableControllerActions { + AjaxSearch { + 1 = pricetable + 2 = searchresult + } + AjaxFilterPanel { + 1 = index + } + AjaxDate { + 1 = dates + } + AjaxTable { + 1 = datestable + 2 = pricetable + 3 = pricetableHtml + 4 = eventPricetable + 5 = daytripRooms + } + AjaxCalendar { + 1 = range + 2 = priceConfig + 3 = priceTable + 4 = priceCalendar + } + AjaxContingent { + 1 = list + 2 = rooms + 3 = ical + } + AjaxWatchlist { + 1 = list + } + AjaxGroupsPrice { + 1 = processForm + } + AjaxTeaserPopup { + 1 = concept + } + } + settings =< plugin.tx_epproducts.settings + persistence =< plugin.tx_epproducts.persistence + view =< plugin.tx_epproducts.view + } +} + +tx_epproducts_ajax_html < tx_epproducts_ajax_json +tx_epproducts_ajax_html.typeNum = 1702 +tx_epproducts_ajax_html.config.additionalHeaders.10.header = Content-type:text/html + +tx_epproducts_ical = PAGE +tx_epproducts_ical { + typeNum = 1710 + config { + disableAllHeaderCode = 1 + disablePrefixComment = 1 + additionalHeaders.10.header = Content-type: text/calendar + xhtml_cleaning = 0 + admPanel = 0 + debug = 0 + } + + 10 = USER + 10 { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = ical + vendorName = EP + controller = Ical + action = contingents + switchableControllerActions { + Ical { + 1 = contingents + } + } + } +} + +lib.bookingWidget = USER +lib.bookingWidget { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = booking + vendorName = EP + controller = Product + action = booking + switchableControllerActions { + Product { + 1 = booking + } + } + settings =< plugin.tx_epproducts.settings + view =< plugin.tx_epproducts.view +} + +# API endpoint for product details (cacheable) +tx_epproducts_api_json = PAGE +tx_epproducts_api_json { + typeNum = 1720 + config { + disableAllHeaderCode = 1 + disablePrefixComment = 1 + additionalHeaders.10.header = Content-type: application/json + additionalHeaders.20.header = Access-Control-Allow-Origin: * + xhtml_cleaning = 0 + admPanel = 0 + debug = 0 + no_cache = 0 + sendCacheHeaders = 1 + } + + 10 = USER + 10 { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = api + vendorName = EP + controller = Api + action = product + switchableControllerActions { + Api { + 1 = product + 2 = hotel + } + } + settings =< plugin.tx_epproducts.settings + persistence =< plugin.tx_epproducts.persistence + } +} diff --git a/packages/ep_products/README.md b/packages/ep_products/README.md new file mode 100644 index 0000000..714800b --- /dev/null +++ b/packages/ep_products/README.md @@ -0,0 +1,17 @@ +Queries for plugin renaming +=========================== + +```mysql +update tt_content set list_type='epproducts_searchbar' where list_type='epproducts_pi1'; +update tt_content set list_type='epproducts_product_detail' where list_type='epproducts_pi3'; +update tt_content set list_type='epproducts_searchresult' where list_type='epproducts_pi2'; +update tt_content set list_type='epproducts_product_teasergroup' where list_type='epproducts_pi4'; +update tt_content set list_type='epproducts_country_detail' where list_type='epproducts_pi5'; +update tt_content set list_type='epproducts_region_teasergroup' where list_type='epproducts_pi6'; +update tt_content set list_type='epproducts_region_detail' where list_type='epproducts_pi10'; +update tt_content set list_type='epproducts_city_teasergroup' where list_type='epproducts_pi7'; +update tt_content set list_type='epproducts_city_detail' where list_type='epproducts_pi11'; +update tt_content set list_type='epproducts_hotel_teasergroup' where list_type='epproducts_pi8'; +update tt_content set list_type='epproducts_hotel_detail' where list_type='epproducts_pi12'; +update tt_content set list_type='epproducts_faq' where list_type='epproducts_pi9'; +``` diff --git a/packages/ep_products/Resources/Private/.htaccess b/packages/ep_products/Resources/Private/.htaccess new file mode 100644 index 0000000..96d0729 --- /dev/null +++ b/packages/ep_products/Resources/Private/.htaccess @@ -0,0 +1,11 @@ +# Apache < 2.3 + + Order allow,deny + Deny from all + Satisfy All + + +# Apache >= 2.3 + + Require all denied + diff --git a/packages/ep_products/Resources/Private/Language/locallang.xlf b/packages/ep_products/Resources/Private/Language/locallang.xlf new file mode 100644 index 0000000..32cf553 --- /dev/null +++ b/packages/ep_products/Resources/Private/Language/locallang.xlf @@ -0,0 +1,68 @@ + + + +
+ + + Termin + + + Produkt + + + Konzept + + + Land + + + Gebiet + + + Stadt/Ort + + + Fact + + + Hotel + + + Faq + + + Code + + + Office-Tipp + + + Mitarbeiter/Teamer + + + Anreise + + + Schneereport + + + Webcam + + + Kontingent + + + Badge + + + Statisches Suchergebnis + + + Vertriebspartner + + + Infobox + + + + diff --git a/packages/ep_products/Resources/Private/Language/locallang_mod.xlf b/packages/ep_products/Resources/Private/Language/locallang_mod.xlf new file mode 100644 index 0000000..1c09931 --- /dev/null +++ b/packages/ep_products/Resources/Private/Language/locallang_mod.xlf @@ -0,0 +1,17 @@ + + + +
+ + + E&P Funktionen + + + Produktspezifische Funktionen + + + Produkte + + + + diff --git a/packages/ep_products/Resources/Private/Layouts/Backend.html b/packages/ep_products/Resources/Private/Layouts/Backend.html new file mode 100644 index 0000000..a606ca0 --- /dev/null +++ b/packages/ep_products/Resources/Private/Layouts/Backend.html @@ -0,0 +1 @@ + diff --git a/packages/ep_products/Resources/Private/Templates/Backend/Index.html b/packages/ep_products/Resources/Private/Templates/Backend/Index.html new file mode 100644 index 0000000..a632d29 --- /dev/null +++ b/packages/ep_products/Resources/Private/Templates/Backend/Index.html @@ -0,0 +1,47 @@ + + + + + +

E&P Funktionen

+ +
+
+

BusPro Daten

+
+
+

+ Letzter Produktdaten-Import: {f:if(condition: productDataUpdatedTimestamp, then: '{productDataUpdatedTimestamp -> f:format.date(format: \'d.m.Y H:i:s\')}', else: '-')} +

+

+ Letzter Kontingente-Import: {f:if(condition: contingentsDataUpdatedTimestamp, then: '{contingentsDataUpdatedTimestamp -> f:format.date(format: \'d.m.Y H:i:s\')}', else: '-')} +

+

+ Hinweis: TYPO3 prüft alle 15 Minuten, ob ein XML Upload aus BusPro erfolgt ist (automatisch + oder manuell) und führt sofort einen Import durch falls erforderlich. +

+
+
+

+ + + Schneehöhen importieren + +

+

+ + + Suchschlitz Auswertung EP Reisen (CSV Download) + +

+

+ + + Suchschlitz Auswertung EP Events (CSV Download) + +

+
+ + diff --git a/packages/ep_products/Resources/Public/Icons/logo.svg b/packages/ep_products/Resources/Public/Icons/logo.svg new file mode 100644 index 0000000..c55419d --- /dev/null +++ b/packages/ep_products/Resources/Public/Icons/logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/packages/ep_products/Resources/Public/Icons/relation.gif b/packages/ep_products/Resources/Public/Icons/relation.gif new file mode 100644 index 0000000..db61d7e Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/relation.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_badge.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_badge.gif new file mode 100644 index 0000000..e6c57ec Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_badge.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_city.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_city.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_city.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_concept.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_concept.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_concept.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_contingent.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_contingent.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_contingent.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_country.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_country.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_country.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_date.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_date.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_date.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_fact.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_fact.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_fact.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_faq.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_faq.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_faq.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_hotel.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_hotel.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_hotel.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_journey.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_journey.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_journey.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_matchcode.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_matchcode.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_matchcode.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_officetip.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_officetip.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_officetip.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_product.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_product.gif new file mode 100644 index 0000000..6cc5f16 Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_product.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_region.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_region.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_region.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_reseller.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_reseller.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_reseller.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_room.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_room.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_room.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_roommapping.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_roommapping.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_roommapping.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_snowreport.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_snowreport.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_snowreport.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_teammember.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_teammember.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_teammember.gif differ diff --git a/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_webcam.gif b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_webcam.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_webcam.gif differ diff --git a/packages/ep_products/composer.json b/packages/ep_products/composer.json new file mode 100644 index 0000000..2319cc5 --- /dev/null +++ b/packages/ep_products/composer.json @@ -0,0 +1,32 @@ +{ + "name": "ep/ep-products", + "description": "Implementation of all product related logic and models", + "license": "GPL-2.0-or-later", + "type": "typo3-cms-extension", + "authors": [ + { + "name": "Björn Fromme", + "email": "fromme@dreipunktnull.com" + } + ], + "require": { + "php": "^8.2", + "league/oauth2-client": "^2.9", + "league/period": "^4.12", + "symfony/http-client": "^7.4", + "typo3/cms-core": "^13.4", + "typo3/cms-extbase": "^13.4", + "typo3/cms-fluid": "^13.4", + "typo3/cms-frontend": "^13.4" + }, + "autoload": { + "psr-4": { + "EP\\EpProducts\\": "Classes/" + } + }, + "extra": { + "typo3/cms": { + "extension-key": "ep_products" + } + } +} diff --git a/packages/ep_products/ext_conf_template.txt b/packages/ep_products/ext_conf_template.txt new file mode 100644 index 0000000..37e6e94 --- /dev/null +++ b/packages/ep_products/ext_conf_template.txt @@ -0,0 +1,95 @@ +# cat=General; type=int; label=Searchresult PID +searchPageUid = 0 + +# cat=MyEp; type=string; label=MyE&P URL +myEpUrl = https://my.ep-reisen.de + +# cat=MyEp; type=string; label=MyE&P Staging URL +myEpStagingUrl = https://my.ep-reisen.net + +# cat=MyEpAPI; type=string; label=MyE&P API base URL (trailing slash!) +myEpApiBaseUrl = + +# cat=MyEpAPI; type=string; label=MyE&P API client id +myEpApiClientId = + +# cat=MyEpAPI; type=string; label=MyE&P API client secret +myEpApiClientSecret = + +# cat=MyEpAPI; type=string; label=MyE&P API authorization URL +myEpApiUrlAuthorize = + +# cat=MyEpAPI; type=string; label=MyE&P API token URL +myEpApiUrlToken = + +# cat=MyEpAPI; type=int; label=MyE&P API inactivity timeout in seconds (0 = PHP default_socket_timeout) +myEpApiTimeout = 5 + +# cat=MyEpAPI; type=int; label=MyE&P API max total request duration in seconds (0 = unlimited) +myEpApiMaxDuration = 15 + +# cat=MyEpAPI; type=string; label=Bpn Connect API base URL +bpnConnectApiBaseUrl = + +# cat=MyEpAPI; type=string; label=Bpn Connect API key +bpnConnectApiKey = + +# cat=MyEpAPI; type=int; label=Bpn Connect API inactivity timeout in seconds (0 = PHP default_socket_timeout) +bpnConnectApiTimeout = 5 + +# cat=MyEpAPI; type=int; label=Bpn Connect API max total request duration in seconds (0 = unlimited) +bpnConnectApiMaxDuration = 15 + +# cat=EpTeamAPI; type=string; label=MyE&P Team base URL (https, e.g. https://team.ep-reisen.de) - receives application form submissions +epTeamApiBaseUrl = + +# cat=EpTeamAPI; type=string; label=MyE&P Team application form API key (sent as X-Api-Key) +epTeamApiKey = + +# cat=EpTeamAPI; type=int; label=MyE&P Team API inactivity timeout in seconds (0 = PHP default_socket_timeout) +epTeamApiTimeout = 5 + +# cat=EpTeamAPI; type=int; label=MyE&P Team API max total request duration in seconds (0 = unlimited) +epTeamApiMaxDuration = 15 + +# cat=SftpImport; type=options[Remote SFTP server=sftp,Local directory (fileadmin/xmlexport)=local,MyE&P API=api]; label=Product/date import source +productImportSource = sftp + +# cat=SftpImport; type=string; label=SFTP host for product/date import +productImportSftpHost = + +# cat=SftpImport; type=int+; label=SFTP port +productImportSftpPort = 22 + +# cat=SftpImport; type=string; label=SFTP username +productImportSftpUser = + +# cat=SftpImport; type=string; label=SFTP password +productImportSftpPassword = + +# cat=SftpImport; type=string; label=Remote directory holding Ziel_*.xml, zustiege.xml and uebertragung.info +productImportSftpRemotePath = / + +# cat=SftpImport; type=string; label=Expected SSH host key (optional; "ssh-ed25519 AAAA..." or "SHA256:..."; empty disables the check) +productImportSftpHostKey = + +# cat=SftpImport; type=int+; label=SFTP connection timeout in seconds +productImportSftpTimeout = 15 + +# cat=ContentFeed; type=string; label=API key for the CMS backchannel endpoints used by MyE&P (/api/hotel, /api/product, /api/content/*); sent as X-Api-Key or ?key=. Empty denies every request. +cmsApiKey = + +# cat=ContentFeed; type=boolean; label=Serve the chatbot content feed (off: all feed endpoints answer 503) +contentFeedEnabled = 1 + +# cat=ContentFeed; type=string; label=Site identifiers included in the chatbot content feed (comma-separated) +contentFeedSites = ep-reisen,ser,ski-boarderweek,ski-wochenenden,snowzone + +# cat=ContentFeed; type=string; label=Language ids included in the chatbot content feed (comma-separated; the English pages are untranslated placeholders today) +contentFeedLanguages = 0 + +# cat=ContentFeed; type=string; label=Page uids whose subtrees stay out of the chatbot content feed (comma-separated; 634 = group booking links) +contentFeedExcludedPages = 634 + +# cat=ContentFeed; type=string; label=News detail page per site for the chatbot content feed (identifier:pageUid, comma-separated) +contentFeedNewsDetailPages = ep-reisen:1392 diff --git a/packages/ep_products/ext_emconf.php b/packages/ep_products/ext_emconf.php new file mode 100644 index 0000000..fd862f1 --- /dev/null +++ b/packages/ep_products/ext_emconf.php @@ -0,0 +1,20 @@ + 'E&P Products', + 'description' => 'Implementation of all product related logic and models', + 'category' => 'plugin', + 'author' => 'Björn Fromme', + 'author_email' => 'fromme@dreipunktnull.com', + 'state' => 'stable', + 'internal' => '', + 'uploadfolder' => '1', + 'createDirs' => '', + 'clearCacheOnLoad' => 0, + 'version' => '1.0.0', + 'constraints' => [ + 'depends' => [], + 'conflicts' => [], + 'suggests' => [], + ], +]; diff --git a/packages/ep_products/ext_icon.gif b/packages/ep_products/ext_icon.gif new file mode 100644 index 0000000..9fc0f1d Binary files /dev/null and b/packages/ep_products/ext_icon.gif differ diff --git a/packages/ep_products/ext_localconf.php b/packages/ep_products/ext_localconf.php new file mode 100644 index 0000000..6e0e7d0 --- /dev/null +++ b/packages/ep_products/ext_localconf.php @@ -0,0 +1,358 @@ + 'ep_products', + 'title' => 'EP Reisedaten Import', + 'description' => 'Importiert Reisedaten aus busProNet', + ]; + + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\EP\EpProducts\Task\ImportContingentsTask::class] = [ + 'extension' => 'ep_products', + 'title' => 'EP Kontingent Import', + 'description' => 'Importiert Kontingentdaten aus busProNet', + ]; + + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\EP\EpProducts\Task\ImportSnowreportTask::class] = [ + 'extension' => 'ep_products', + 'title' => 'EP Schneehöhen Import', + 'description' => 'Importiert Schneehöhen', + ]; + + $GLOBALS['TYPO3_CONF_VARS']['LOG']['EP']['EpProducts']['Service']['GroupsSwissService']['writerConfiguration'] = [ + \TYPO3\CMS\Core\Log\LogLevel::INFO => [ + 'TYPO3\\CMS\\Core\\Log\\Writer\\FileWriter' => [ + 'logFile' => \TYPO3\CMS\Core\Core\Environment::getVarPath() . '/log/groups.swiss.log', + ], + ], + ]; + + $iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class); + $iconRegistry->registerIcon( + 'ep-logo', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:ep_products/Resources/Public/Icons/logo.svg'] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'searchbar', + [ + 'Searchbar' => 'index', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'searchresult', + [ + 'Search' => 'searchresult,processSearch', + ], + [ + 'Search' => 'searchresult,processSearch', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'product_detail', + [ + 'Product' => 'detail,hotelList', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'product_teasergroup', + [ + 'Product' => 'teasergroup', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'product_pricetable', + [ + 'Product' => 'pricetable', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'country_detail', + [ + 'Country' => 'detail', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'region_teasergroup', + [ + 'Region' => 'teasergroup', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'region_detail', + [ + 'Region' => 'detail', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'city_teasergroup', + [ + 'City' => 'teasergroup', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'city_detail', + [ + 'City' => 'detail', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'hotel_teasergroup', + [ + 'Hotel' => 'teasergroup', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'hotel_teasergroup_nodate', + [ + 'Hotel' => 'dateIndependentTeasergroup', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'hotel_detail', + [ + 'Hotel' => 'detail', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'hotel_detail_event', + [ + 'Hotel' => 'detailEvent', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'faq', + [ + 'Faq' => 'detail', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'concept_teasergroup', + [ + 'Concept' => 'teasergroup', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'concept_detail', + [ + 'Concept' => 'detail', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'snowreport_list', + [ + 'Snowreport' => 'list', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'teammembers', + [ + 'Team' => 'list, show', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'newsletter', + [ + 'Newsletter' => 'index', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'newsletter_subscription', + [ + 'Newsletter' => 'subscriptionForm,subscriptionFormSubmit', + ], + [ + 'Newsletter' => 'subscriptionForm,subscriptionFormSubmit', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'maps', + [ + 'Map' => 'general', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'pricetable', + [ + 'Pricetable' => 'event', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'reseller', + [ + 'Reseller' => 'list,copypaste', + ], + [ + 'Reseller' => 'list,copypaste', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'watchlist', + [ + 'Watchlist' => 'list', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'travelinfo', + [ + 'Travelinfo' => 'travelInfo,travelCode', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'ajax', + [ + 'AjaxSearch' => 'searchresult', + 'AjaxFilterPanel' => 'index', + 'AjaxDate' => 'dates', + 'AjaxContingent' => 'list,rooms', + 'AjaxTable' => 'datestable,pricetable,pricetableHtml,eventPricetable,daytripRooms', + 'AjaxCalendar' => 'range,priceConfig,priceCalendar,priceTable', + 'AjaxWatchlist' => 'list', + 'AjaxTeaserPopup' => 'concept', + ], + [ + 'AjaxSearch' => 'searchresult', + 'AjaxFilterPanel' => 'index', + 'AjaxDate' => 'dates', + 'AjaxContingent' => 'list,rooms', + 'AjaxTable' => 'datestable,pricetable,pricetableHtml,eventPricetable,daytripRooms', + 'AjaxCalendar' => 'range', + 'AjaxWatchlist' => 'list', + 'AjaxTeaserPopup' => 'concept', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'ical', + [ + 'Ical' => 'contingents', + ], + [ + 'Ical' => 'contingents', + ] + ); + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'EP.ep_products', + 'api', + [ + 'Api' => 'product,hotel', + ] + ); + + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem']['ep_products'] = + \EP\EpProducts\Hooks\PageLayoutView::class; + + $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = '_gl'; + $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'gclid'; + $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'ref'; + $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'pa'; + $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'ep_staging'; + // NOTE: tx_epproducts_ajax[months|month|year] were previously excluded from the cHash. + // That was harmless while every AjaxCalendar action was non-cacheable, but priceCalendar, + // priceConfig and priceTable are cacheable now, and an excluded parameter is dropped from + // the page-cache key as well - so the 2026 and 2027 price tables shared one entry and the + // second year was served the first year's content. They must take part in the cHash. + // The client-side callers that vary month/year (calendar_controller.js) send them in a + // POST body, which the cHash never inspects, so nothing depends on the exclusion. + $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'f'; + + $GLOBALS['TYPO3_CONF_VARS']['SYS']['routing']['aspects']['PassthroughMapper'] = + \EP\EpProducts\Routing\Aspect\PassthroughMapper::class; + + $GLOBALS['TYPO3_CONF_VARS']['LOG']['EP']['EpProducts']['Controller']['writerConfiguration'] = [ + \TYPO3\CMS\Core\Log\LogLevel::INFO => [ + \EP\EpProducts\Log\Writer::class => [ + 'logTable' => 'tx_epproducts_log' + ], + ], + ]; + + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['teamSlug'] = + \EP\EpProducts\Updates\TeamSlugUpdater::class; + + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options']['tables']['tx_epproducts_log'] = [ + 'dateField' => 'time_micro', + 'expirePeriod' => '180', + ]; + + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options']['tables']['tx_epevents_log'] = [ + 'dateField' => 'time_micro', + 'expirePeriod' => '180', + ]; +}; + +$boot(); +unset($boot); diff --git a/packages/ep_products/ext_tables.php b/packages/ep_products/ext_tables.php new file mode 100644 index 0000000..84ec6b2 --- /dev/null +++ b/packages/ep_products/ext_tables.php @@ -0,0 +1,22 @@ + 'index, importProducts, importContingents, importSnowReport, dumpSearchLogEpreisen, dumpSearchLogEpevents', + ], + [ + 'access' => 'user,group', + 'labels' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang_mod.xlf', + 'icon' => 'EXT:ep_products/Resources/Public/Icons/logo.svg' + ] + ); + +}); diff --git a/packages/ep_products/ext_tables.sql b/packages/ep_products/ext_tables.sql new file mode 100644 index 0000000..2a54011 --- /dev/null +++ b/packages/ep_products/ext_tables.sql @@ -0,0 +1,1698 @@ +# noinspection SqlNoDataSourceInspectionForFile +# +# Table structure for table 'tx_epproducts_domain_model_product' +# +CREATE TABLE tx_epproducts_domain_model_product +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + searchable tinyint(4) unsigned DEFAULT '1' NOT NULL, + bookable tinyint(4) unsigned DEFAULT '1' NOT NULL, + season varchar(64) DEFAULT '' NOT NULL, + hide_booking_button tinyint(4) unsigned DEFAULT '0' NOT NULL, + daytrip tinyint(4) unsigned DEFAULT '0' NOT NULL, + important tinyint(4) unsigned DEFAULT '0' NOT NULL, + skipass_included tinyint(4) unsigned DEFAULT '1' NOT NULL, + name varchar(255) DEFAULT '' NOT NULL, + name_internal varchar(255) DEFAULT '' NOT NULL, + subline varchar(255) DEFAULT '' NOT NULL, + headline varchar(255) DEFAULT '' NOT NULL, + code varchar(255) DEFAULT '' NOT NULL, + feature varchar(255) DEFAULT '' NOT NULL, + bus_pro_id int(11) unsigned NOT NULL default '0', + detail_page varchar(255) DEFAULT '' NOT NULL, + external_link varchar(255) DEFAULT '' NOT NULL, + tour_link varchar(255) DEFAULT '' NOT NULL, + path_segment varchar(255) DEFAULT '' NOT NULL, + teaser text NOT NULL, + teaser_long text NOT NULL, + header_title varchar(255) DEFAULT '' NOT NULL, + header_subtitle varchar(255) DEFAULT '' NOT NULL, + keywords text NOT NULL, + concept_description text NOT NULL, + board_description text NOT NULL, + additional_services text NOT NULL, + included_services text NOT NULL, + programme_description text NOT NULL, + ski_pass_alt_title varchar(255) DEFAULT '' NOT NULL, + ski_pass_description text NOT NULL, + rating_average varchar(255) DEFAULT '' NOT NULL, + rating_votes_count int(11) unsigned NOT NULL default '0', + testimonials text NOT NULL, + teaser_images int(11) unsigned NOT NULL default '0', + header_images int(11) unsigned NOT NULL default '0', + images int(11) unsigned NOT NULL default '0', + reseller_images int(11) unsigned NOT NULL default '0', + tour_thumbnail int(11) unsigned NOT NULL default '0', + concept int(11) unsigned DEFAULT '0' NOT NULL, + country int(11) unsigned DEFAULT '0' NOT NULL, + region int(11) unsigned DEFAULT '0' NOT NULL, + additional_regions int(11) unsigned DEFAULT '0' NOT NULL, + city int(11) unsigned DEFAULT '0' NOT NULL, + journey int(11) unsigned DEFAULT '0' NOT NULL, + facts int(11) unsigned DEFAULT '0' NOT NULL, + dates int(11) unsigned DEFAULT '0' NOT NULL, + hotels int(11) unsigned DEFAULT '0' NOT NULL, + calendar_hotel int(11) unsigned DEFAULT '0' NOT NULL, + faqs int(11) unsigned DEFAULT '0' NOT NULL, + officetip int(11) unsigned DEFAULT '0' NOT NULL, + alt_product int(11) unsigned DEFAULT '0' NOT NULL, + alt_label varchar(255) DEFAULT '' NOT NULL, + video varchar(255) DEFAULT '' NOT NULL, + banner int(11) unsigned NOT NULL default '0', + badge int(11) unsigned NOT NULL default '0', + non_bookable_info int(11) unsigned NOT NULL default '0', + non_bookable_dates varchar(255) DEFAULT '' NOT NULL, + min_price int(11) unsigned NOT NULL default '0', + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_date' +# +CREATE TABLE tx_epproducts_domain_model_date +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + bus_pro_id int(11) DEFAULT '0' NOT NULL, + hotel_bus_pro_id int(11) DEFAULT '0' NOT NULL, + code varchar(255) DEFAULT '' NOT NULL, + season varchar(64) DEFAULT '' NOT NULL, + date_start date DEFAULT '0000-00-00', + date_end date DEFAULT '0000-00-00', + nights int(11) DEFAULT '0' NOT NULL, + board varchar(255) DEFAULT '' NOT NULL, + board_bus_pro_id int(11) DEFAULT '0' NOT NULL, + board_code varchar(64) DEFAULT '' NOT NULL, + bus_included tinyint(1) unsigned DEFAULT '0' NOT NULL, + bus_available tinyint(1) unsigned DEFAULT '0' NOT NULL, + skipass_included tinyint(1) unsigned DEFAULT '0' NOT NULL, + group_travel tinyint(1) unsigned DEFAULT '0' NOT NULL, + bus_price decimal(10,2) DEFAULT '0.00' NOT NULL, + services_included text NOT NULL, + services_general text NOT NULL, + courses_included text NOT NULL, + pickups text NOT NULL, + guide text NOT NULL, + discount decimal(10,2) DEFAULT '0.00' NOT NULL, + min_price decimal(10,2) DEFAULT '0.00' NOT NULL, + pseudo_price decimal(10,2) DEFAULT '0.00' NOT NULL, + available int(11) DEFAULT '0' NOT NULL, + min_pax int(11) DEFAULT '0' NOT NULL, + product int(11) unsigned DEFAULT '0', + product_pid int(11) unsigned DEFAULT '0', + daytrip tinyint(1) unsigned DEFAULT '0' NOT NULL, + hide_booking_button tinyint(1) unsigned DEFAULT '0' NOT NULL, + important tinyint(1) unsigned DEFAULT '0' NOT NULL, + product_name varchar(255) DEFAULT '' NOT NULL, + product_subline varchar(255) DEFAULT '' NOT NULL, + product_fact varchar(255) DEFAULT '' NOT NULL, + product_searchable tinyint(4) unsigned DEFAULT '0' NOT NULL, + product_detail_page varchar(255) DEFAULT '' NOT NULL, + product_keywords text NOT NULL, + product_teaser text NOT NULL, + product_feature varchar(255) DEFAULT '' NOT NULL, + product_slug varchar(255) DEFAULT '' NOT NULL, + concept int(11) unsigned DEFAULT '0', + concept_name varchar(255) DEFAULT '' NOT NULL, + concept_code varchar(32) DEFAULT '' NOT NULL, + concept_sorting int(11) DEFAULT '0' NOT NULL, + concept_popup_text text NOT NULL, + country int(11) unsigned DEFAULT '0', + country_name varchar(255) DEFAULT '' NOT NULL, + country_code char(2) DEFAULT '' NOT NULL, + country_keywords text NOT NULL, + region int(11) unsigned DEFAULT '0', + region_name varchar(255) DEFAULT '' NOT NULL, + region_fact varchar(255) DEFAULT '' NOT NULL, + region_keywords text NOT NULL, + region_feature varchar(255) DEFAULT '' NOT NULL, + city int(11) unsigned DEFAULT '0', + city_name varchar(255) DEFAULT '' NOT NULL, + city_keywords text NOT NULL, + hotel int(11) unsigned DEFAULT '0', + hotel_code varchar(32) DEFAULT '' NOT NULL, + hotel_category int(11) DEFAULT '0' NOT NULL, + hotel_name varchar(255) DEFAULT '' NOT NULL, + hotel_header_title varchar(255) DEFAULT '' NOT NULL, + hotel_fact varchar(255) DEFAULT '' NOT NULL, + hotel_type tinyint(4) unsigned DEFAULT '0' NOT NULL, + hotel_keywords text NOT NULL, + hotel_teaser text NOT NULL, + hotel_detail_page varchar(255) DEFAULT '' NOT NULL, + earlybird tinyint(4) unsigned DEFAULT '0' NOT NULL, + new tinyint(4) unsigned DEFAULT '0' NOT NULL, + low_contingent tinyint(4) unsigned DEFAULT '0' NOT NULL, + rooms int(11) unsigned DEFAULT '0', + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid, product_pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid), + KEY aggregates (date_start, date_end, min_price), + KEY relations (product, date_start), + KEY contingents (hotel_code, date_start), + KEY groups (product, hotel) +); + +# +# Table structure for table 'tx_epproducts_domain_model_hotel' +# +CREATE TABLE tx_epproducts_domain_model_hotel +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + earlybird tinyint(4) unsigned DEFAULT '0' NOT NULL, + new tinyint(4) unsigned DEFAULT '0' NOT NULL, + low_contingent tinyint(4) unsigned DEFAULT '0' NOT NULL, + show_as_teaser tinyint(4) unsigned DEFAULT '0' NOT NULL, + detail_page varchar(255) DEFAULT '' NOT NULL, + name varchar(255) DEFAULT '' NOT NULL, + short_name varchar(255) DEFAULT '' NOT NULL, + headline varchar(255) DEFAULT '' NOT NULL, + description text NOT NULL, + keywords text NOT NULL, + features text NOT NULL, + room_types text NOT NULL, + additional_information text NOT NULL, + teaser text NOT NULL, + header_title varchar(255) DEFAULT '' NOT NULL, + header_subtitle varchar(255) DEFAULT '' NOT NULL, + teaser_images int(11) unsigned NOT NULL default '0', + header_images int(11) unsigned NOT NULL default '0', + images int(11) unsigned NOT NULL default '0', + reseller_images int(11) unsigned NOT NULL default '0', + code varchar(255) DEFAULT '' NOT NULL, + external_link varchar(255) DEFAULT '' NOT NULL, + path_segment varchar(255) DEFAULT '' NOT NULL, + type varchar(255) DEFAULT '' NOT NULL, + category int(11) DEFAULT '0' NOT NULL, + country int(11) unsigned DEFAULT '0', + region int(11) unsigned DEFAULT '0', + city int(11) unsigned DEFAULT '0', + teamer int(11) unsigned DEFAULT '0' NOT NULL, + products int(11) unsigned DEFAULT '0' NOT NULL, + rooms int(11) unsigned DEFAULT '0' NOT NULL, + facts int(11) unsigned DEFAULT '0' NOT NULL, + additional_codes int(11) unsigned DEFAULT '0' NOT NULL, + mappings int(11) unsigned DEFAULT '0' NOT NULL, + address varchar(255) DEFAULT '' NOT NULL, + latitude varchar(255) DEFAULT '' NOT NULL, + longitude varchar(255) DEFAULT '' NOT NULL, + groups_price_configs int(11) unsigned NOT NULL default '0', + groups_price_options int(11) unsigned NOT NULL default '0', + groups_price_boards int(11) unsigned NOT NULL default '0', + groups_price_adolescents_age int(11) unsigned NOT NULL default '0', + groups_price_tax_label varchar(255) DEFAULT '' NOT NULL, + main_season_from date DEFAULT NULL, + main_season_to date DEFAULT NULL, + beds_count int(11) DEFAULT '0' NOT NULL, + parking_count int(11) DEFAULT '0' NOT NULL, + altitude varchar(255) DEFAULT '' NOT NULL, + shower varchar(255) DEFAULT '' NOT NULL, + partyroom tinyint(4) unsigned DEFAULT '0' NOT NULL, + gamesroom tinyint(4) unsigned DEFAULT '0' NOT NULL, + sauna tinyint(4) unsigned DEFAULT '0' NOT NULL, + wifi tinyint(4) unsigned DEFAULT '0' NOT NULL, + distance_bus varchar(255) DEFAULT '' NOT NULL, + distance_lift varchar(255) DEFAULT '' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid), + KEY idx_hotel_uid (uid, deleted, hidden, starttime, endtime) +); + +# +# Table structure for table 'tx_epproducts_domain_model_groupspriceconfig' +# +CREATE TABLE tx_epproducts_domain_model_groupspriceconfig +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + date_from int(11) unsigned DEFAULT '0' NOT NULL, + date_to int(11) unsigned DEFAULT '0' NOT NULL, + persons_included int(11) unsigned DEFAULT '0' NOT NULL, + price float(8) unsigned DEFAULT '0' NOT NULL, + price_chf float(8) unsigned DEFAULT '0' NOT NULL, + price_additional_person float(8) unsigned DEFAULT '0' NOT NULL, + price_additional_person_chf float(8) unsigned DEFAULT '0' NOT NULL, + price_additional_person_group_1 float(8) unsigned DEFAULT '0' NOT NULL, + price_additional_person_group_1_chf float(8) unsigned DEFAULT '0' NOT NULL, + price_additional_person_group_2 float(8) unsigned DEFAULT '0' NOT NULL, + price_additional_person_group_2_chf float(8) unsigned DEFAULT '0' NOT NULL, + price_additional_person_group_3 float(8) unsigned DEFAULT '0' NOT NULL, + price_additional_person_group_3_chf float(8) unsigned DEFAULT '0' NOT NULL, + bonus_card_included tinyint(4) unsigned DEFAULT '0' NOT NULL, + hotel int(11) unsigned DEFAULT '0', + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_groupspriceoption' +# +CREATE TABLE tx_epproducts_domain_model_groupspriceoption +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + title varchar(255) DEFAULT '' NOT NULL, + price float(8) unsigned DEFAULT '0' NOT NULL, + price_chf float(8) unsigned DEFAULT '0' NOT NULL, + type tinyint(4) unsigned DEFAULT '0' NOT NULL, + ignore_with_board tinyint(4) unsigned DEFAULT '0' NOT NULL, + hotel int(11) unsigned DEFAULT '0', + sorting int(11) DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_groupspriceboard' +# +CREATE TABLE tx_epproducts_domain_model_groupspriceboard +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + title varchar(255) DEFAULT '' NOT NULL, + price float(8) unsigned DEFAULT '0' NOT NULL, + price_chf float(8) unsigned DEFAULT '0' NOT NULL, + hotel int(11) unsigned DEFAULT '0', + sorting int(11) DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_room' +# +CREATE TABLE tx_epproducts_domain_model_room +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + bus_pro_id int(11) DEFAULT '0' NOT NULL, + name varchar(255) DEFAULT '' NOT NULL, + code varchar(255) DEFAULT '' NOT NULL, + type int(11) DEFAULT '0' NOT NULL, + price decimal(10,2) DEFAULT '0.00' NOT NULL, + services text NOT NULL, + nights int(11) DEFAULT '0' NOT NULL, + pax int(11) DEFAULT '0' NOT NULL, + available int(11) DEFAULT '0' NOT NULL, + status int(11) DEFAULT '0' NOT NULL, + date int(11) unsigned DEFAULT '0', + hotel int(11) unsigned DEFAULT '0', + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid), + KEY constraints (date, type) +); + +# +# Table structure for table 'tx_epproducts_domain_model_concept' +# +CREATE TABLE tx_epproducts_domain_model_concept +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + sorting int(11) DEFAULT '0' NOT NULL, + bus_pro_id int(11) DEFAULT '0' NOT NULL, + name varchar(255) DEFAULT '' NOT NULL, + code varchar(255) DEFAULT '' NOT NULL, + description text NOT NULL, + description_extended text NOT NULL, + detail_page varchar(255) DEFAULT '' NOT NULL, + products int(11) DEFAULT '0' NOT NULL, + teaser text NOT NULL, + popup_title varchar(255) DEFAULT '' NOT NULL, + popup_text text NOT NULL, + popup_button_label varchar(255) DEFAULT '' NOT NULL, + headline varchar(255) DEFAULT '' NOT NULL, + header_title varchar(255) DEFAULT '' NOT NULL, + header_subtitle varchar(255) DEFAULT '' NOT NULL, + teaser_images int(11) unsigned NOT NULL default '0', + header_images int(11) unsigned NOT NULL default '0', + images int(11) unsigned NOT NULL default '0', + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_country' +# +CREATE TABLE tx_epproducts_domain_model_country +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + detail_page varchar(255) DEFAULT '' NOT NULL, + code char(2) DEFAULT '' NOT NULL, + name varchar(255) DEFAULT '' NOT NULL, + name_internal varchar(255) DEFAULT '' NOT NULL, + season char(1) DEFAULT '' NOT NULL, + headline varchar(255) DEFAULT '' NOT NULL, + teaser text NOT NULL, + header_title varchar(255) DEFAULT '' NOT NULL, + header_subtitle varchar(255) DEFAULT '' NOT NULL, + keywords text NOT NULL, + description text NOT NULL, + description_extended text NOT NULL, + teaser_images int(11) unsigned NOT NULL default '0', + header_images int(11) unsigned NOT NULL default '0', + images int(11) unsigned NOT NULL default '0', + latitude varchar(255) DEFAULT '' NOT NULL, + longitude varchar(255) DEFAULT '' NOT NULL, + facts int(11) unsigned DEFAULT '0' NOT NULL, + officetip int(11) unsigned DEFAULT '0' NOT NULL, + snowreport int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_region' +# +CREATE TABLE tx_epproducts_domain_model_region +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + detail_page varchar(255) DEFAULT '' NOT NULL, + exclude_from_teasergroups tinyint(4) unsigned DEFAULT '0' NOT NULL, + name varchar(255) DEFAULT '' NOT NULL, + name_internal varchar(255) DEFAULT '' NOT NULL, + season char(1) DEFAULT '' NOT NULL, + headline varchar(255) DEFAULT '' NOT NULL, + teaser text NOT NULL, + header_title varchar(255) DEFAULT '' NOT NULL, + header_subtitle varchar(255) DEFAULT '' NOT NULL, + keywords text NOT NULL, + description text NOT NULL, + ski_area text NOT NULL, + ski_area_extended text NOT NULL, + ski_pass text NOT NULL, + news text NOT NULL, + length varchar(255) DEFAULT '' NOT NULL, + height varchar(255) DEFAULT '' NOT NULL, + lifts varchar(255) DEFAULT '' NOT NULL, + feature varchar(255) DEFAULT '' NOT NULL, + teaser_images int(11) unsigned NOT NULL default '0', + header_images int(11) unsigned NOT NULL default '0', + images int(11) unsigned NOT NULL default '0', + region_maps int(11) unsigned NOT NULL default '0', + latitude varchar(255) DEFAULT '' NOT NULL, + longitude varchar(255) DEFAULT '' NOT NULL, + country int(11) unsigned DEFAULT '0', + cities int(11) unsigned DEFAULT '0' NOT NULL, + products int(11) unsigned DEFAULT '0' NOT NULL, + facts int(11) unsigned DEFAULT '0' NOT NULL, + hotels int(11) unsigned DEFAULT '0' NOT NULL, + snowreport int(11) unsigned DEFAULT '0', + webcam int(11) unsigned DEFAULT '0', + officetip int(11) unsigned DEFAULT '0', + award_image int(11) unsigned NOT NULL default '0', + award_link varchar(255) DEFAULT '' NOT NULL, + award_label varchar(255) DEFAULT '' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_city' +# +CREATE TABLE tx_epproducts_domain_model_city +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + detail_page varchar(255) DEFAULT '' NOT NULL, + exclude_from_teasergroups tinyint(4) unsigned DEFAULT '0' NOT NULL, + name varchar(255) DEFAULT '' NOT NULL, + name_internal varchar(255) DEFAULT '' NOT NULL, + season char(1) DEFAULT '' NOT NULL, + headline varchar(255) DEFAULT '' NOT NULL, + teaser text NOT NULL, + header_title varchar(255) DEFAULT '' NOT NULL, + header_subtitle varchar(255) DEFAULT '' NOT NULL, + keywords text NOT NULL, + description text NOT NULL, + description_extended text NOT NULL, + party text NOT NULL, + leisure text NOT NULL, + teaser_images int(11) unsigned NOT NULL default '0', + header_images int(11) unsigned NOT NULL default '0', + images int(11) unsigned NOT NULL default '0', + latitude varchar(255) DEFAULT '' NOT NULL, + longitude varchar(255) DEFAULT '' NOT NULL, + country int(11) unsigned DEFAULT '0', + region int(11) unsigned DEFAULT '0', + officetip int(11) unsigned DEFAULT '0', + facts int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_fact' +# +CREATE TABLE tx_epproducts_domain_model_fact +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + label varchar(255) DEFAULT '' NOT NULL, + name varchar(255) DEFAULT '' NOT NULL, + keywords text NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_matchcode' +# +CREATE TABLE tx_epproducts_domain_model_matchcode +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + code varchar(255) DEFAULT '' NOT NULL, + entity int(11) unsigned DEFAULT '0', + entity_type varchar(255) DEFAULT '' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid), + KEY idx_matchcode_entity_type (entity, entity_type, deleted, hidden, starttime, endtime) +); + +# +# Table structure for table 'tx_epproducts_domain_model_faq' +# +CREATE TABLE tx_epproducts_domain_model_faq +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + category varchar(255) DEFAULT '' NOT NULL, + label varchar(255) DEFAULT '' NOT NULL, + question varchar(255) DEFAULT '' NOT NULL, + answer text NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_officetip' +# +CREATE TABLE tx_epproducts_domain_model_officetip +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + label varchar(255) DEFAULT '' NOT NULL, + text text NOT NULL, + author int(11) unsigned DEFAULT '0', + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_teammember' +# +CREATE TABLE tx_epproducts_domain_model_teammember +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + name varchar(255) DEFAULT '' NOT NULL, + path_segment varchar(255) DEFAULT '' NOT NULL, + email varchar(255) DEFAULT '' NOT NULL, + phone varchar(255) DEFAULT '' NOT NULL, + tasks varchar(255) DEFAULT '' NOT NULL, + description text NOT NULL, + image int(11) DEFAULT '0' NOT NULL, + sorting int(11) DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_journey' +# +CREATE TABLE tx_epproducts_domain_model_journey +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + name varchar(255) DEFAULT '' NOT NULL, + by_car text NOT NULL, + by_bus text NOT NULL, + image int(11) DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_snowreport' +# +CREATE TABLE tx_epproducts_domain_model_snowreport +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + vendor_uid int(11) unsigned DEFAULT '0', + region_name varchar(255) DEFAULT '' NOT NULL, + glacier tinyint(4) unsigned DEFAULT '0' NOT NULL, + alpes tinyint(4) unsigned DEFAULT '0' NOT NULL, + height_max int(11) DEFAULT '0' NOT NULL, + height_min int(11) DEFAULT '0' NOT NULL, + opened tinyint(4) unsigned DEFAULT '0' NOT NULL, + snow_level_mountain int(11) DEFAULT '0' NOT NULL, + snow_level_valley int(11) DEFAULT '0' NOT NULL, + snow_quality varchar(255) DEFAULT '' NOT NULL, + last_snowfall int(11) unsigned DEFAULT '0' NOT NULL, + lifts_total int(11) DEFAULT '0' NOT NULL, + lifts_opened int(11) DEFAULT '0' NOT NULL, + last_update int(11) unsigned DEFAULT '0' NOT NULL, + webcams int(11) unsigned DEFAULT '0', + season_start int(11) unsigned DEFAULT '0' NOT NULL, + season_end int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY vendor_uid (vendor_uid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_webcam' +# +CREATE TABLE tx_epproducts_domain_model_webcam +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + vendor_uid varchar(32) DEFAULT '' NOT NULL, + available tinyint(4) unsigned DEFAULT '0' NOT NULL, + snowreport int(11) unsigned DEFAULT '0', + name varchar(255) DEFAULT '' NOT NULL, + url varchar(255) DEFAULT '' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY vendor_uid (vendor_uid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_contingent' +# +CREATE TABLE tx_epproducts_domain_model_contingent +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + pax int(11) unsigned DEFAULT '0' NOT NULL, + available int(11) unsigned DEFAULT '0' NOT NULL, + date date DEFAULT '0000-00-00', + hotel int(11) unsigned DEFAULT '0', + hotel_code varchar(64) DEFAULT '' NOT NULL, + hotel_bus_pro_id varchar(64) DEFAULT '' NOT NULL, + room_code varchar(128) DEFAULT '' NOT NULL, + room_label varchar(255) DEFAULT '' NOT NULL, + status tinyint(4) unsigned DEFAULT '0' NOT NULL, + min_price int(11) DEFAULT '0' NOT NULL, + min_nights int(11) DEFAULT '0' NOT NULL, + additional_night_min_price int(11) DEFAULT '0' NOT NULL, + additional_night_min_nights int(11) DEFAULT '0' NOT NULL, + groupsch_id varchar(8) DEFAULT '' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid), + KEY groups (date, hotel), + KEY idx_contingent_hotel_date (hotel, date, deleted, hidden, starttime, endtime), + KEY idx_contingent_hotel_code_date (hotel_code, date, deleted, hidden, starttime, endtime) +); + +# +# Table structure for table 'tx_epproducts_domain_model_badge' +# +CREATE TABLE tx_epproducts_domain_model_badge +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + title varchar(255) DEFAULT '' NOT NULL, + subtitle varchar(255) DEFAULT '' NOT NULL, + link varchar(255) DEFAULT '' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_staticsearchparams' +# +CREATE TABLE tx_epproducts_domain_model_staticsearchparams +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + name varchar(255) DEFAULT '' NOT NULL, + path_segment varchar(255) DEFAULT '' NOT NULL, + date_from date DEFAULT '0000-00-00', + date_to date DEFAULT '0000-00-00', + bus tinyint(1) unsigned DEFAULT '0' NOT NULL, + product int(11) unsigned DEFAULT '0', + concept varchar(255) DEFAULT '' NOT NULL, + country int(11) unsigned DEFAULT '0', + region int(11) unsigned DEFAULT '0', + hotel int(11) unsigned DEFAULT '0', + hotel_types varchar(255) DEFAULT '' NOT NULL, + pax int(11) unsigned DEFAULT '0', + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid), + KEY path_segment (path_segment) +); + +# +# Table structure for table 'tx_epproducts_domain_model_reseller' +# +CREATE TABLE tx_epproducts_domain_model_reseller +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + name varchar(255) DEFAULT '' NOT NULL, + code varchar(255) DEFAULT '' NOT NULL, + pa_code varchar(255) DEFAULT '' NOT NULL, + products int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_infobox' +# +CREATE TABLE tx_epproducts_domain_model_infobox +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + title varchar(255) DEFAULT '' NOT NULL, + text text NOT NULL, + default_text tinyint(4) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_travelinfo' +# +CREATE TABLE tx_epproducts_domain_model_travelinfo +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + title varchar(255) DEFAULT '' NOT NULL, + headline varchar(255) DEFAULT '' NOT NULL, + subline varchar(255) DEFAULT '' NOT NULL, + intro_text text DEFAULT '0' NOT NULL, + bus_info_text text DEFAULT '0' NOT NULL, + departure_text text DEFAULT '0' NOT NULL, + footer_text text DEFAULT '0' NOT NULL, + self_arranged_text text DEFAULT '0' NOT NULL, + additional_info text DEFAULT '0' NOT NULL, + important_phone_numbers text DEFAULT '0' NOT NULL, + important_links text DEFAULT '0' NOT NULL, + header_image int(11) DEFAULT '0' NOT NULL, + footer_image int(11) DEFAULT '0' NOT NULL, + links int(11) DEFAULT '0' NOT NULL, + teasers int(11) DEFAULT '0' NOT NULL, + product int(11) unsigned DEFAULT '0', + hide_address tinyint(4) unsigned DEFAULT '0' NOT NULL, + travel_code varchar(255) DEFAULT '' NOT NULL, + path_segment varchar(255) DEFAULT '' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_travelinfoteaser' +# +CREATE TABLE tx_epproducts_domain_model_travelinfoteaser +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + title varchar(255) DEFAULT '' NOT NULL, + bodytext text DEFAULT '0' NOT NULL, + image int(11) DEFAULT '0' NOT NULL, + category varchar(255) DEFAULT '' NOT NULL, + + tx_epproducts_travelinfo int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_travelinfolink' +# +CREATE TABLE tx_epproducts_domain_model_travelinfolink +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + title varchar(255) DEFAULT '' NOT NULL, + link varchar(255) DEFAULT '' NOT NULL, + icon varchar(255) DEFAULT '' NOT NULL, + + tx_epproducts_travelinfo int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_epproducts_domain_model_hotel_mapping' +# +CREATE TABLE tx_epproducts_domain_model_hotel_mapping +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + product int(11) unsigned DEFAULT '0' NOT NULL, + detail_page varchar(255) DEFAULT '' NOT NULL, + hotel int(11) unsigned DEFAULT '0', + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'sys_domain' +# +CREATE TABLE sys_domain +( + tx_epproducts_reseller int(11) unsigned DEFAULT '0' +); + +# +# Table structure for table 'tx_epproducts_product_hotel_mm' +# +CREATE TABLE tx_epproducts_product_hotel_mm +( + uid_local int(11) unsigned DEFAULT '0' NOT NULL, + uid_foreign int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +# +# Table structure for table 'tx_epproducts_product_region_mm' +# +CREATE TABLE tx_epproducts_product_region_mm +( + uid_local int(11) unsigned DEFAULT '0' NOT NULL, + uid_foreign int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +# +# Table structure for table 'tx_epproducts_hotel_teammember_mm' +# +CREATE TABLE tx_epproducts_hotel_teammember_mm +( + uid_local int(11) unsigned DEFAULT '0' NOT NULL, + uid_foreign int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +# +# Table structure for table 'tx_epproducts_product_faq_mm' +# +CREATE TABLE tx_epproducts_product_faq_mm +( + uid_local int(11) unsigned DEFAULT '0' NOT NULL, + uid_foreign int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +# +# Table structure for table 'tx_epproducts_product_fact_mm' +# +CREATE TABLE tx_epproducts_product_fact_mm +( + uid_local int(11) unsigned DEFAULT '0' NOT NULL, + uid_foreign int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +# +# Table structure for table 'tx_epproducts_hotel_fact_mm' +# +CREATE TABLE tx_epproducts_hotel_fact_mm +( + uid_local int(11) unsigned DEFAULT '0' NOT NULL, + uid_foreign int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +# +# Table structure for table 'tx_epproducts_country_fact_mm' +# +CREATE TABLE tx_epproducts_country_fact_mm +( + uid_local int(11) unsigned DEFAULT '0' NOT NULL, + uid_foreign int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +# +# Table structure for table 'tx_epproducts_region_fact_mm' +# +CREATE TABLE tx_epproducts_region_fact_mm +( + uid_local int(11) unsigned DEFAULT '0' NOT NULL, + uid_foreign int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +# +# Table structure for table 'tx_epproducts_city_fact_mm' +# +CREATE TABLE tx_epproducts_city_fact_mm +( + uid_local int(11) unsigned DEFAULT '0' NOT NULL, + uid_foreign int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +# +# Table structure for table 'tx_epproducts_reseller_product_mm' +# +CREATE TABLE tx_epproducts_reseller_product_mm +( + uid_local int(11) unsigned DEFAULT '0' NOT NULL, + uid_foreign int(11) unsigned DEFAULT '0' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +# +# Table structure for table 'tx_epproducts_log' +# +CREATE TABLE tx_epproducts_log +( + request_id varchar(13) DEFAULT '' NOT NULL, + time_micro double(16, 4) NOT NULL default '0.0000', + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + component varchar(255) DEFAULT '' NOT NULL, + level tinyint(1) unsigned DEFAULT '0' NOT NULL, + message text, + data text, + + KEY request (request_id) +); diff --git a/packages/ep_theme/Classes/.htaccess b/packages/ep_theme/Classes/.htaccess new file mode 100755 index 0000000..3418e55 --- /dev/null +++ b/packages/ep_theme/Classes/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/packages/ep_theme/Classes/Controller/BackendController.php b/packages/ep_theme/Classes/Controller/BackendController.php new file mode 100644 index 0000000..151b4be --- /dev/null +++ b/packages/ep_theme/Classes/Controller/BackendController.php @@ -0,0 +1,83 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpTheme\Domain\Repository\PageRepository; +use TYPO3\CMS\Backend\View\BackendTemplateView; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class BackendController extends ActionController +{ + /** + * Backend Template Container + * + * @var BackendTemplateView + */ + protected $defaultViewObjectName = BackendTemplateView::class; + + /** + * BackendTemplateContainer + * + * @var BackendTemplateView + */ + protected $view; + + /** + * @var int + */ + protected $pageUid; + + /** + * @var PageRepository + */ + protected $pageRepository; + + /** + * @param PageRepository $pageRepository + */ + public function __construct(PageRepository $pageRepository) + { + $this->pageRepository = $pageRepository; + } + + public function initializeAction() + { + $this->pageUid = (int)GeneralUtility::_GET('id'); + + parent::initializeAction(); + } + + public function listPagesAction() + { + $pages = $this->pageRepository->findPagesByPid($this->pageUid, true); + + $this->view->assign('pages', $pages); + } +} diff --git a/packages/ep_theme/Classes/DataProcessing/ContextProcessor.php b/packages/ep_theme/Classes/DataProcessing/ContextProcessor.php new file mode 100644 index 0000000..52d5d52 --- /dev/null +++ b/packages/ep_theme/Classes/DataProcessing/ContextProcessor.php @@ -0,0 +1,130 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\ResellerDataService; +use EP\EpProducts\Traits\DbConnectionTrait; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; +use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface; + +class ContextProcessor implements DataProcessorInterface +{ + use DbConnectionTrait; + + /** + * @param ContentObjectRenderer $cObj + * @param array $contentObjectConfiguration + * @param array $processorConfiguration + * @param array $processedData + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function process( + ContentObjectRenderer $cObj, + array $contentObjectConfiguration, + array $processorConfiguration, + array $processedData + ) { + $data = $cObj->data; + $processedData['context'] = []; + $countryUid = (int) $data['tx_eptheme_context_country']; + if ($countryUid > 0) { + $processedData['context']['country'] = $this->getContextRecord($countryUid, 'tx_epproducts_domain_model_country'); + } + $regionUid = (int) $data['tx_eptheme_context_region']; + if ($regionUid > 0) { + $processedData['context']['region'] = $this->getContextRecord($regionUid, 'tx_epproducts_domain_model_region'); + } + if ($data['tx_eptheme_context_pacode']) { + $processedData['context']['paCode'] = $data['tx_eptheme_context_pacode']; + } elseif ($paCode = $this->getPaCodeFromRequest()) { + $processedData['context']['paCode'] = $paCode; + } + + return $processedData; + } + + /** + * @return array|bool + * @throws \Doctrine\DBAL\DBALException + */ + private function getResellerData() + { + /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + /** @var ResellerDataService $resellerService */ + $resellerService = $objectManager->get(ResellerDataService::class); + return $resellerService->getResellerForCurrentDomain(); + } + + /** + * @return string + */ + private function getPaCodeFromRequest() + { + $currentUri = GeneralUtility::getIndpEnv('REQUEST_URI'); + $query = parse_url($currentUri, \PHP_URL_QUERY); + + if (!$query) { + return ''; + } + + parse_str($query, $items); + + if (array_key_exists('pa', $items)) { + return $items['pa']; + } + + return ''; + } + + /** + * @param int $uid + * @param string $table + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + private function getContextRecord($uid, $table) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $query = $qb + ->select('*') + ->from($table) + ->where('uid = :uid') + ->andWhere('deleted = 0') + ->andWhere('hidden = 0') + ->setParameter('uid', $uid) + ; + + return $query->execute()->fetch(); + } + +} diff --git a/packages/ep_theme/Classes/DataProcessing/FlexFormProcessor.php b/packages/ep_theme/Classes/DataProcessing/FlexFormProcessor.php new file mode 100644 index 0000000..55e3790 --- /dev/null +++ b/packages/ep_theme/Classes/DataProcessing/FlexFormProcessor.php @@ -0,0 +1,60 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Service\FlexFormService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; +use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface; + +class FlexFormProcessor implements DataProcessorInterface +{ + + /** + * @param ContentObjectRenderer $cObj + * @param array $contentObjectConfiguration + * @param array $processorConfiguration + * @param array $processedData + * + * @return array + */ + public function process( + ContentObjectRenderer $cObj, + array $contentObjectConfiguration, + array $processorConfiguration, + array $processedData + ) { + $data = $cObj->data; + if (array_key_exists('pi_flexform', $data)) { + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + $processedData['data']['flexForm'] = $flexFormService->convertFlexFormContentToArray($data['pi_flexform']); + } + return $processedData; + } + +} diff --git a/packages/ep_theme/Classes/DataProcessing/ProductRatingProcessor.php b/packages/ep_theme/Classes/DataProcessing/ProductRatingProcessor.php new file mode 100644 index 0000000..7880a0d --- /dev/null +++ b/packages/ep_theme/Classes/DataProcessing/ProductRatingProcessor.php @@ -0,0 +1,86 @@ +getDbConnection()->createQueryBuilder(); + + $result = $qb + ->selectLiteral('SUM(product.rating_votes_count) as totalCount', 'SUM(product.rating_average) as totalAverage') + ->from('tx_epproducts_domain_model_product', 'product') + ->execute() + ->fetchAssociative() + ; + + $totalCount = $result['totalCount']; + $totalAverage = $result['totalAverage']; + + $result = $qb + ->selectLiteral('COUNT(*) as ratedCount') + ->from('tx_epproducts_domain_model_product', 'product') + ->where($qb->expr()->gt('product.rating_average', 0)) + ->execute() + ->fetchAssociative() + ; + + $ratedCount = $result['ratedCount']; + + $jsonData = [ + '@context' => 'https://schema.org', + '@type' => 'LocalBusiness', + 'name' => 'E&P Reisen und Events GmbH', + 'address' => [ + '@type' => 'PostalAddress', + 'streetAddress' => 'Aachener Str. 326-328', + 'postalCode' => '50933', + 'addressLocality' => 'Köln,Deutschland' + ], + 'telephone' => '00492212722760', + 'url' => 'https://www.ep-reisen.de/', + 'contactPoint' => [ + '@type' => 'ContactPoint', + 'telephone' => '+492212722760', + 'email' => 'info@ep-reisen.de', + 'contactType' => 'customer service', + 'areaServed' => 'DE', + 'availableLanguage' => 'German' + ], + 'logo' => 'https://www.ep-reisen.de/typo3conf/ext/ep_theme/Resources/Public/images/logo_wide.svg', + 'priceRange' => '€', + 'image' => 'https://www.ep-reisen.de/typo3conf/ext/ep_theme/Resources/Public/images/logo_wide.svg', + 'aggregateRating' => [ + '@type' => 'AggregateRating', + 'ratingValue' => number_format($totalAverage/$ratedCount, 2), + 'bestRating' => '5', + 'worstRating' => '1', + 'ratingCount' => $totalCount + ], + 'sameAs' => [ + 'https://www.facebook.com/ep.reisen.events', + 'https://www.instagram.com/ep_reisen/', + 'https://www.youtube.com/user/epreisende' + ] + ]; + + $processedData['totalRatings'] = [ + 'totalCount' => $totalCount, + 'totalAverage' => $totalAverage/$ratedCount, + 'jsonData' => $jsonData, + ]; + + return $processedData; + } +} diff --git a/packages/ep_theme/Classes/DataProcessing/SliderProcessor.php b/packages/ep_theme/Classes/DataProcessing/SliderProcessor.php new file mode 100644 index 0000000..466ce5a --- /dev/null +++ b/packages/ep_theme/Classes/DataProcessing/SliderProcessor.php @@ -0,0 +1,116 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Traits\DbConnectionTrait; +use TYPO3\CMS\Core\Resource\FileRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\RootlineUtility; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; +use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface; + +class SliderProcessor implements DataProcessorInterface +{ + use DbConnectionTrait; + + /** + * @param ContentObjectRenderer $cObj + * @param array $contentObjectConfiguration + * @param array $processorConfiguration + * @param array $processedData + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function process( + ContentObjectRenderer $cObj, + array $contentObjectConfiguration, + array $processorConfiguration, + array $processedData + ) { + $page = $cObj->data; + $pageUid = $page['uid']; + + if ((int) $page['tx_eptheme_slides'] === 0) { + $pageUid = $this->getParentPageWithSlides(); + } + + if ($pageUid === null) { + return $processedData; + } + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $query = $qb + ->select('uid', 'headline', 'subline', 'button', 'page_uid', 'skipass') + ->from('tx_eptheme_domain_model_slide') + ->where('page = :pageUid') + ->andWhere('deleted = 0') + ->andWhere('hidden = 0') + ->orderBy('sorting') + ->setParameter('pageUid', $pageUid) + ; + + $slides = $query->execute()->fetchAll(); + + if ($slides) { + $fileRepository = GeneralUtility::makeInstance(FileRepository::class); + foreach ($slides as $slide) { + $processedData['slides'][$slide['uid']] = $slide; + $processedData['slides'][$slide['uid']]['image'] = $fileRepository->findByRelation( + 'tx_eptheme_domain_model_slide', + 'images', + $slide['uid'] + ); + } + } + + if (1 === count($slides)) { + $processedData['slide'] = reset($processedData['slides']); + } + + return $processedData; + } + + /** + * @return null|int + */ + protected function getParentPageWithSlides() + { + $rootlineUtility = new RootlineUtility($GLOBALS['TSFE']->id); + $rootline = $rootlineUtility->get(); + + foreach ($rootline as $parentPage) { + if ((int) $parentPage['tx_eptheme_slides'] > 0) { + return (int) $parentPage['uid']; + } + } + + return null; + } + +} diff --git a/packages/ep_theme/Classes/DataProcessing/TeaserImageProcessor.php b/packages/ep_theme/Classes/DataProcessing/TeaserImageProcessor.php new file mode 100644 index 0000000..3553c2d --- /dev/null +++ b/packages/ep_theme/Classes/DataProcessing/TeaserImageProcessor.php @@ -0,0 +1,59 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; +use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface; +use TYPO3\CMS\Frontend\Resource\FileCollector; + +class TeaserImageProcessor implements DataProcessorInterface +{ + + /** + * @param ContentObjectRenderer $cObj + * @param array $contentObjectConfiguration + * @param array $processorConfiguration + * @param array $processedData + * + * @return array + */ + public function process( + ContentObjectRenderer $cObj, + array $contentObjectConfiguration, + array $processorConfiguration, + array $processedData + ) { + $imageFileCollector = GeneralUtility::makeInstance(FileCollector::class); + $imageFileCollector->addFilesFromRelation('tt_content', 'assets', $cObj->data); + $processedData['teaserImage'] = reset($imageFileCollector->getFiles()); + + return $processedData; + } + +} diff --git a/packages/ep_theme/Classes/DataProcessing/TeaserProcessor.php b/packages/ep_theme/Classes/DataProcessing/TeaserProcessor.php new file mode 100644 index 0000000..1cbd7e2 --- /dev/null +++ b/packages/ep_theme/Classes/DataProcessing/TeaserProcessor.php @@ -0,0 +1,130 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\FilterSettings; +use EP\EpProducts\Domain\Repository\CityRepository; +use EP\EpProducts\Domain\Repository\ConceptRepository; +use EP\EpProducts\Domain\Repository\CountryRepository; +use EP\EpProducts\Domain\Repository\RegionRepository; +use EP\EpProducts\Service\HotelDataService; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; +use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface; + +class TeaserProcessor implements DataProcessorInterface +{ + + /** + * @param ContentObjectRenderer $cObj + * @param array $contentObjectConfiguration + * @param array $processorConfiguration + * @param array $processedData + * + * @return array + */ + public function process( + ContentObjectRenderer $cObj, + array $contentObjectConfiguration, + array $processorConfiguration, + array $processedData + ) { + /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + + if ((int) $processedData['data']['tx_eptheme_context_city'] > 0) { + /** @var \EP\EpProducts\Domain\Repository\CityRepository $repository */ + $repository = $objectManager->get(CityRepository::class); + /** @var \EP\EpProducts\Domain\Model\City $city */ + $city = $repository->findByIdentifier($processedData['data']['tx_eptheme_context_city']); + $processedData['city'] = $city; + } + if ((int) $processedData['data']['tx_eptheme_context_region'] > 0) { + /** @var \EP\EpProducts\Domain\Repository\RegionRepository $repository */ + $repository = $objectManager->get(RegionRepository::class); + /** @var \EP\EpProducts\Domain\Model\Region $region */ + $region = $repository->findByIdentifier($processedData['data']['tx_eptheme_context_region']); + $processedData['region'] = $region; + } + if ((int) $processedData['data']['tx_eptheme_context_country'] > 0) { + /** @var \EP\EpProducts\Domain\Repository\CountryRepository $repository */ + $repository = $objectManager->get(CountryRepository::class); + /** @var \EP\EpProducts\Domain\Model\Country $country */ + $country = $repository->findByIdentifier($processedData['data']['tx_eptheme_context_country']); + $processedData['country'] = $country; + } + if ((int) $processedData['data']['tx_eptheme_context_hotel'] > 0) { + /** @var \EP\EpProducts\Service\HotelDataService $service */ + $service = $objectManager->get(HotelDataService::class); + /** @var array $hotel */ + $hotel = $service->getTeaser($processedData['data']['tx_eptheme_context_hotel']); + $processedData['hotel'] = $hotel; + } + if ((int) $processedData['data']['tx_eptheme_context_concept'] > 0) { + /** @var \EP\EpProducts\Domain\Repository\ConceptRepository $repository */ + $repository = $objectManager->get(ConceptRepository::class); + /** @var \EP\EpProducts\Domain\Model\Concept $concept */ + $concept = $repository->findByIdentifier($processedData['data']['tx_eptheme_context_concept']); + $processedData['concept'] = $concept; + } + if ((int) $processedData['data']['tx_eptheme_context_product'] > 0) { + $productUid = (int) $processedData['data']['tx_eptheme_context_product']; + $hotelUid = (int) $processedData['data']['tx_eptheme_context_hotel']; + + $filterSettings = [ + 'productUid' => $productUid, + 'hotelUid' => $hotelUid, + ]; + + $dateFromTimestamp = (int) $processedData['data']['tx_eptheme_context_date_from']; + if ($dateFromTimestamp > 0) { + $dateFrom = new \DateTime(); + $dateFrom->setTimestamp($dateFromTimestamp); + $filterSettings['dateFrom'] = $dateFrom->format('Y-m-d'); + } + $dateToTimestamp = (int) $processedData['data']['tx_eptheme_context_date_to']; + if ($dateToTimestamp > 0) { + $dateTo = new \DateTime(); + $dateTo->setTimestamp($dateToTimestamp); + $filterSettings['dateTo'] = $dateTo->format('Y-m-d'); + } + + /** @var \EP\EpProducts\Service\ProductDataService $productDataService */ + $productDataService = $objectManager->get(ProductDataService::class); + $processedData['teaser'] = $productDataService->getTeaser($filterSettings); + $processedData['teaser']['forceHotelUid'] = $hotelUid; + } + + $processedData['referringPageUid'] = $GLOBALS['TSFE']->id; + + return $processedData; + } + +} diff --git a/packages/ep_theme/Classes/Domain/Model/Dto/ContactForm.php b/packages/ep_theme/Classes/Domain/Model/Dto/ContactForm.php new file mode 100644 index 0000000..5959573 --- /dev/null +++ b/packages/ep_theme/Classes/Domain/Model/Dto/ContactForm.php @@ -0,0 +1,351 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class ContactForm +{ + const FORM_TYPE_SIMPLE = 1; + const FORM_TYPE_FULL = 2; + + /** + * @var int + */ + protected $formType = 1; + + /** + * Honeypot field!! + * + * @var string + */ + protected $subject; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $name; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $firstName; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $gender; + + /** + * @var string + */ + protected $company; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + * @TYPO3\CMS\Extbase\Annotation\Validate("EmailAddress") + */ + protected $email; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $phone; + + /** + * @var string + */ + protected $period; + + /** + * @var string + */ + protected $duration; + + /** + * @var string + */ + protected $pax; + + /** + * @var string + */ + protected $age; + + /** + * @var string + */ + protected $children; + + /** + * @var string + */ + protected $inquiry; + + /** + * @var string + */ + protected $pageUrl; + + /** + * @param string $pageUrl + * @param int $formType + */ + public function __construct($pageUrl = null, $formType = 1) + { + $this->pageUrl = $pageUrl; + $this->formType = $formType; + } + + /** + * @return string + */ + public function getSubject() + { + return $this->subject; + } + + /** + * @param string $subject + */ + public function setSubject($subject) + { + $this->subject = $subject; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getFirstName() + { + return $this->firstName; + } + + /** + * @param string $firstName + */ + public function setFirstName($firstName) + { + $this->firstName = $firstName; + } + + /** + * @return string + */ + public function getGender() + { + return $this->gender; + } + + /** + * @param string $gender + */ + public function setGender($gender) + { + $this->gender = $gender; + } + + /** + * @return string + */ + public function getCompany() + { + return $this->company; + } + + /** + * @param string $company + */ + public function setCompany($company) + { + $this->company = $company; + } + + /** + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * @param string $email + */ + public function setEmail($email) + { + $this->email = $email; + } + + /** + * @return string + */ + public function getPhone() + { + return $this->phone; + } + + /** + * @param string $phone + */ + public function setPhone($phone) + { + $this->phone = $phone; + } + + /** + * @return string + */ + public function getPeriod() + { + return $this->period; + } + + /** + * @param string $period + */ + public function setPeriod($period) + { + $this->period = $period; + } + + /** + * @return string + */ + public function getDuration() + { + return $this->duration; + } + + /** + * @param string $duration + */ + public function setDuration($duration) + { + $this->duration = $duration; + } + + /** + * @return string + */ + public function getPax() + { + return $this->pax; + } + + /** + * @param string $pax + */ + public function setPax($pax) + { + $this->pax = $pax; + } + + /** + * @return string + */ + public function getAge() + { + return $this->age; + } + + /** + * @param string $age + */ + public function setAge($age) + { + $this->age = $age; + } + + /** + * @return string + */ + public function getChildren() + { + return $this->children; + } + + /** + * @param string $children + */ + public function setChildren($children) + { + $this->children = $children; + } + + /** + * @return string + */ + public function getInquiry() + { + return $this->inquiry; + } + + /** + * @param string $inquiry + */ + public function setInquiry($inquiry) + { + $this->inquiry = $inquiry; + } + + /** + * @return string + */ + public function getPageUrl() + { + return $this->pageUrl; + } + + /** + * @param string $pageUrl + */ + public function setPageUrl($pageUrl) + { + $this->pageUrl = $pageUrl; + } + +} diff --git a/packages/ep_theme/Classes/Domain/Model/Page.php b/packages/ep_theme/Classes/Domain/Model/Page.php new file mode 100644 index 0000000..d028e9d --- /dev/null +++ b/packages/ep_theme/Classes/Domain/Model/Page.php @@ -0,0 +1,102 @@ +hidden; + } + + /** + * @param bool $hidden + */ + public function setHidden($hidden) + { + $this->hidden = $hidden; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @return string + */ + public function getSeoTitle() + { + return $this->seoTitle; + } + + /** + * @param string $seoTitle + */ + public function setSeoTitle($seoTitle) + { + $this->seoTitle = $seoTitle; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } +} diff --git a/packages/ep_theme/Classes/Domain/Repository/PageRepository.php b/packages/ep_theme/Classes/Domain/Repository/PageRepository.php new file mode 100644 index 0000000..f764de1 --- /dev/null +++ b/packages/ep_theme/Classes/Domain/Repository/PageRepository.php @@ -0,0 +1,35 @@ + 'ASC', + ]; + + public function findPagesByPid(int $pid, bool $includeDisabled = false) + { + $query = $this->createQuery(); + $query + ->getQuerySettings() + ->setRespectStoragePage(false) + ->setIgnoreEnableFields($includeDisabled) + ->setIncludeDeleted(false) + ->setRespectSysLanguage(false) + ; + $query->matching($query->logicalAnd([ + $query->equals('pid', $pid), + $query->equals('doktype', CorePageRepository::DOKTYPE_DEFAULT), + $query->equals('navHide', false), + ])); + + return $query->execute(); + } +} diff --git a/packages/ep_theme/Classes/Exception/ConfigurationMissingException.php b/packages/ep_theme/Classes/Exception/ConfigurationMissingException.php new file mode 100644 index 0000000..a59dd0a --- /dev/null +++ b/packages/ep_theme/Classes/Exception/ConfigurationMissingException.php @@ -0,0 +1,6 @@ + local form element identifier. + */ + protected function parseMapping(): array + { + $mappings = []; + $mappingOption = (string)$this->parseOption('mapping'); + + foreach (GeneralUtility::trimExplode(',', $mappingOption, true) as $mapping) { + $parts = GeneralUtility::trimExplode(':', $mapping, true, 2); + if (2 !== count($parts)) { + continue; + } + + [$remote, $local] = $parts; + if ('' === $remote || '' === $local) { + continue; + } + + $mappings[$remote] = $local; + } + + return $mappings; + } + + /** + * The mapped values, keyed by the remote name the receiving side expects. Values pass through + * normalizeValue() just like the labeled ones do, so a date element arrives as a Y-m-d string + * instead of a serialized DateTime. A value that does not survive normalization keeps its key: + * the remote names make up the request shape and dropping one would change it. + */ + protected function buildPayload(): array + { + $values = []; + $formValues = $this->finisherContext->getFormValues(); + + foreach ($this->parseMapping() as $remote => $local) { + if (false === array_key_exists($local, $formValues)) { + continue; + } + + $values[$remote] = $this->normalizeValue($formValues[$local], $local); + } + + return $values; + } + + /** + * Every submitted value that the mapping does not already cover, keyed by the element label + * as the editor sees it in the form editor. Forms built in the backend use generated + * identifiers (text-1, singleselect-2, ...) that carry no meaning and cannot be renamed + * without breaking existing finisher options, so the label is the only stable description + * of what was asked. + * + * The label is taken from the form definition, not through TranslationService: a translated + * label would change the payload keys with the site language, and these keys are stored. + */ + protected function buildLabeledValues(): array + { + $formValues = $this->finisherContext->getFormValues(); + $skip = array_merge( + array_values($this->parseMapping()), + GeneralUtility::trimExplode(',', (string)$this->parseOption('exclude'), true) + ); + + // Keyed by identifier in form order, and contains only real form elements - iterating + // this instead of the form values keeps the collection in the order of the form and + // leaves out anything that is not an element the visitor filled in. + $elements = $this->finisherContext->getFormRuntime()->getFormDefinition()->getElements(); + + $collected = []; + foreach ($elements as $identifier => $element) { + if (true === in_array($identifier, $skip, true) || false === array_key_exists($identifier, $formValues)) { + continue; + } + + $value = $this->normalizeValue($formValues[$identifier], $identifier); + if (null === $value || '' === $value || [] === $value) { + continue; + } + + $label = trim($element->getLabel()); + if ('' === $label) { + $label = $identifier; + } + if (true === array_key_exists($label, $collected)) { + $label .= ' (' . $identifier . ')'; + } + + $collected[$label] = $value; + } + + return $collected; + } + + /** + * @param mixed $value + * + * @return mixed null when the value has no JSON representation + */ + protected function normalizeValue($value, string $identifier) + { + if (true === is_array($value)) { + $normalized = []; + foreach ($value as $item) { + $item = $this->normalizeValue($item, $identifier); + if (null !== $item && '' !== $item) { + $normalized[] = $item; + } + } + + return $normalized; + } + + if ($value instanceof \DateTimeInterface) { + return $value->format('Y-m-d'); + } + + if ($value instanceof FileReference) { + $resource = $value->getOriginalResource(); + + return null !== $resource ? $resource->getName() : null; + } + + if (true === is_object($value)) { + $this->logWarning('Form value skipped while collecting labeled values: unsupported type.', [ + 'element' => $identifier, + 'class' => get_class($value), + ]); + + return null; + } + + return $value; + } + + protected function logWarning(string $message, array $context = []): void + { + GeneralUtility::makeInstance(LogManager::class) + ->getLogger(static::class) + ->warning($message, array_merge([ + 'finisher' => static::class, + ], $context)); + } +} diff --git a/packages/ep_theme/Classes/Form/AddReferrerFinisher.php b/packages/ep_theme/Classes/Form/AddReferrerFinisher.php new file mode 100644 index 0000000..ec01204 --- /dev/null +++ b/packages/ep_theme/Classes/Form/AddReferrerFinisher.php @@ -0,0 +1,74 @@ +addCurrentUrlField(); + $this->addReferrerField(); + } + + private function addReferrerField(): void + { + // Look for key 'referrer' in frontend session + $referrer = $GLOBALS['TSFE']->fe_user->getKey('ses', 'referrer'); + + if (null === $referrer) { + return; + } + + // Add textfield to form containing referrer value + $field = $this->finisherContext + ->getFormRuntime() + ->getFormDefinition() + ->getPageByIndex(0) + ->createElement('referrer', 'Text'); + + $field->setDefaultValue($referrer); + $field->setDataType('string'); + $field->setLabel('Referrer'); + + // Add finisher variable for potential later use + $this->finisherContext->getFinisherVariableProvider()->add( + $this->shortFinisherIdentifier, + 'referrer', + $referrer + ); + + // Remove session key + $GLOBALS['TSFE']->fe_user->setKey('ses', 'referrer', null); + $GLOBALS['TSFE']->fe_user->storeSessionData(); + } + + private function addCurrentUrlField(): void + { + // Get URL of current page + $pageId = $GLOBALS['TSFE']->id; + $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId); + $url = $site->getRouter()->generateUri($pageId); + + // Add textfield to form containing url value + $field = $this->finisherContext + ->getFormRuntime() + ->getFormDefinition() + ->getPageByIndex(0) + ->createElement('pageurl', 'Text'); + + $field->setDefaultValue($url); + $field->setDataType('string'); + $field->setLabel('URL'); + + // Add finisher variable for potential later use + $this->finisherContext->getFinisherVariableProvider()->add( + $this->shortFinisherIdentifier, + 'pageurl', + $url + ); + } +} diff --git a/packages/ep_theme/Classes/Form/ApplicationApiFinisher.php b/packages/ep_theme/Classes/Form/ApplicationApiFinisher.php new file mode 100644 index 0000000..1a30cda --- /dev/null +++ b/packages/ep_theme/Classes/Form/ApplicationApiFinisher.php @@ -0,0 +1,83 @@ +apiClient = $apiClient; + } + + protected function executeInternal() + { + try { + $payload = $this->buildPayload(); + if ([] === $payload) { + $this->logWarning('Application form API finisher skipped: the mapping produced no values.'); + + return; + } + + $formDefinition = $this->finisherContext->getFormRuntime()->getFormDefinition(); + $formLabel = trim($formDefinition->getLabel()); + + $payload['form'] = $formDefinition->getIdentifier(); + // The name the editor gave the form, so the receiving side can label a submission + // without keeping a list of identifiers. Taken from the definition rather than + // through TranslationService: a translated label would differ per site language. + $payload['formLabel'] = '' !== $formLabel ? $formLabel : $formDefinition->getIdentifier(); + $payload['submittedAt'] = (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM); + $payload = array_merge($payload, $this->resolvePageContext()); + $payload['details'] = $this->buildLabeledValues(); + + $this->apiClient->submitApplicationForm($payload); + } catch (\Throwable $e) { + $this->logWarning('Application form API finisher request failed.', [ + 'exception' => $e, + ]); + } + } + + /** + * The page the form was submitted from. The URL is generated through the site router in the + * visitor's language, the same way AddReferrerFinisher does it, so it is the page's canonical + * URL rather than the submit request with its form arguments attached. + */ + private function resolvePageContext(): array + { + if (false === isset($GLOBALS['TSFE']) || 0 === (int)$GLOBALS['TSFE']->id) { + return []; + } + + $pageUid = (int)$GLOBALS['TSFE']->id; + $context = ['pageUid' => $pageUid]; + + try { + $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageUid); + $context['pageUrl'] = (string)$site->getRouter()->generateUri($pageUid, [ + '_language' => $GLOBALS['TSFE']->getLanguage(), + ]); + } catch (\Throwable $e) { + // An unroutable page must not cost the submission, so the uid is sent without a URL. + $this->logWarning('Application form API finisher could not resolve the page URL.', [ + 'pageUid' => $pageUid, + 'exception' => $e, + ]); + } + + return $context; + } +} diff --git a/packages/ep_theme/Classes/Form/BpnApiFinisher.php b/packages/ep_theme/Classes/Form/BpnApiFinisher.php new file mode 100644 index 0000000..c115ebb --- /dev/null +++ b/packages/ep_theme/Classes/Form/BpnApiFinisher.php @@ -0,0 +1,35 @@ +apiClient = $apiClient; + } + + protected function executeInternal() + { + try { + $values = $this->buildPayload(); + if ([] !== $values) { + $this->apiClient->registerAddress($values); + } + } catch (\Throwable $e) { + $this->logWarning('Bpn API finisher request failed.', [ + 'exception' => $e, + ]); + } + } +} diff --git a/packages/ep_theme/Classes/Form/WebhookFinisher.php b/packages/ep_theme/Classes/Form/WebhookFinisher.php new file mode 100644 index 0000000..1c94395 --- /dev/null +++ b/packages/ep_theme/Classes/Form/WebhookFinisher.php @@ -0,0 +1,40 @@ +parseOption('url')); + if ('' === $url) { + return; + } + + $payload = $this->buildPayload(); + if ([] === $payload) { + return; + } + + $response = HttpClient::create()->request('POST', $url, [ + 'json' => $payload, + ]); + + $statusCode = $response->getStatusCode(); + if ($statusCode < 200 || $statusCode >= 300) { + $this->logWarning('Webhook finisher returned an unexpected response status.', [ + 'url' => $url, + 'statusCode' => $statusCode, + ]); + } + } catch (\Throwable $e) { + $this->logWarning('Webhook finisher request failed.', [ + 'exception' => $e, + ]); + } + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/AbstractPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/AbstractPreviewRenderer.php new file mode 100644 index 0000000..7765f64 --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/AbstractPreviewRenderer.php @@ -0,0 +1,146 @@ +cType)) { + throw new ConfigurationMissingException('Property cType must not be empty', 1586703436); + } + } + + public function preProcess( + PageLayoutView &$parentObject, + &$drawItem, + &$headerContent, + &$itemContent, + array &$row + ) { + $this->data = &$row; + if ($this->isCtypeMatching() && $this->checkTemplateFile()) { + $drawItem = false; + $headerContent = $this->getHeaderContent(); + $itemContent .= $this->getBodytext(); + } + } + + protected function getHeaderContent(): string + { + return '
'; + } + + protected function getBodytext(): string + { + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $standaloneView = $objectManager->get(StandaloneView::class); + $standaloneView->setTemplatePathAndFilename($this->getTemplateFile()); + $standaloneView->assignMultiple($this->getAssignmentsForTemplate() + [ + 'data' => $this->data, + 'flexForm' => $this->getFlexForm(), + 'firstImage' => count($this->getImages()) > 0 ? $this->getImages()[0] : null, + 'images' => $this->getImages() + ]); + return $standaloneView->render(); + } + + protected function getAssignmentsForTemplate(): array + { + return []; + } + + protected function getFlexForm(): array + { + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $flexFormService = $objectManager->get(FlexFormService::class); + return $flexFormService->convertFlexFormContentToArray($this->data['pi_flexform']); + } + + protected function getImages(int $uid = null, string $tablenames = 'tt_content', string $fieldname = 'image'): array + { + $uid = $uid ?? $this->data['uid']; + $references = []; + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $fileRepository = $objectManager->get(FileRepository::class); + /** @var ConnectionPool $connectionPool */ + $connectionPool = $objectManager->get(ConnectionPool::class); + $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference'); + $identifiers = (array)$queryBuilder + ->select('uid') + ->from('sys_file_reference') + ->where('uid_foreign=' . $uid . ' and tablenames="' . $tablenames . '" and fieldname="' . $fieldname . '"') + ->execute() + ->fetch(\PDO::FETCH_COLUMN); + foreach ($identifiers as $identifier) { + if ($identifier > 0) { + $reference = $fileRepository->findFileReferenceByUid($identifier); + if ($reference !== null) { + $references[] = $reference; + } + } + } + return $references; + } + + /** + * @return bool + */ + protected function isCtypeMatching(): bool + { + return $this->data['CType'] === $this->cType; + } + + protected function checkTemplateFile(): bool + { + if (is_file($this->getTemplateFile()) === false) { + throw new TemplateFileMissingException( + 'Expected template file for preview rendering for CType ' . $this->cType . ' is missing', + 1586703260 + ); + } + return true; + } + + protected function getTemplateFile(): string + { + return GeneralUtility::getFileAbsFileName( + $this->templatePath . GeneralUtility::underscoredToUpperCamelCase($this->data['CType']) . '.html' + ); + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/DisrupterPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/DisrupterPreviewRenderer.php new file mode 100644 index 0000000..523635b --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/DisrupterPreviewRenderer.php @@ -0,0 +1,20 @@ +data['tx_eptheme_disrupter']; + $content = BackendUtility::getRecord('tx_eptheme_domain_model_disrupter', $uid); + + return [ + 'content' => $content, + ]; + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/DisturberPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/DisturberPreviewRenderer.php new file mode 100644 index 0000000..088fed6 --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/DisturberPreviewRenderer.php @@ -0,0 +1,8 @@ +data['tx_eptheme_context_region']; + if ((int)$uid > 0) { + $content = BackendUtility::getRecord('tx_epproducts_domain_model_region', $uid); + } else { + $uid = $this->data['tx_eptheme_context_hotel']; + $content = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $uid); + + } + + return [ + 'content' => $content, + ]; + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/LineupPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/LineupPreviewRenderer.php new file mode 100644 index 0000000..d176373 --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/LineupPreviewRenderer.php @@ -0,0 +1,31 @@ +get(ConnectionPool::class); + $queryBuilder = $connectionPool->getQueryBuilderForTable('tx_eptheme_domain_model_lineup'); + $rows = $queryBuilder->select('*') + ->from('tx_eptheme_domain_model_lineup') + ->where('tt_content='.$this->data['uid']) + ->orderBy('sorting', 'ASC') + ->execute() + ->fetchAllAssociative(); + + return [ + 'rows' => $rows, + ]; + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserBookingLinkPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserBookingLinkPreviewRenderer.php new file mode 100644 index 0000000..b89fe57 --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserBookingLinkPreviewRenderer.php @@ -0,0 +1,21 @@ +data['tx_eptheme_context_product']; + $images = $this->getImages($productUid, 'tx_epproducts_domain_model_product', 'teaser_images'); + $product = BackendUtility::getRecord('tx_epproducts_domain_model_product', $productUid); + return [ + 'images' => $images, + 'product' => $product, + ]; + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserCityPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserCityPreviewRenderer.php new file mode 100644 index 0000000..e8e1068 --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserCityPreviewRenderer.php @@ -0,0 +1,20 @@ +data['tx_eptheme_context_city']; + $images = $this->getImages($uid, 'tx_epproducts_domain_model_city', 'teaser_images'); + return [ + 'images' => $images, + 'city' => BackendUtility::getRecord('tx_epproducts_domain_model_city', $uid), + ]; + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserConceptPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserConceptPreviewRenderer.php new file mode 100644 index 0000000..ea52ab9 --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserConceptPreviewRenderer.php @@ -0,0 +1,20 @@ +data['tx_eptheme_context_concept']; + $images = $this->getImages($uid, 'tx_epproducts_domain_model_concept', 'teaser_images'); + return [ + 'images' => $images, + 'concept' => BackendUtility::getRecord('tx_epproducts_domain_model_concept', $uid), + ]; + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserCountryPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserCountryPreviewRenderer.php new file mode 100644 index 0000000..35ccbcc --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserCountryPreviewRenderer.php @@ -0,0 +1,20 @@ +data['tx_eptheme_context_country']; + $images = $this->getImages($countryUid, 'tx_epproducts_domain_model_country', 'teaser_images'); + return [ + 'images' => $images, + 'country' => BackendUtility::getRecord('tx_epproducts_domain_model_country', $countryUid), + ]; + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserHotelPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserHotelPreviewRenderer.php new file mode 100644 index 0000000..43d95d5 --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserHotelPreviewRenderer.php @@ -0,0 +1,20 @@ +data['tx_eptheme_context_hotel']; + $images = $this->getImages($uid, 'tx_epproducts_domain_model_hotel', 'teaser_images'); + return [ + 'images' => $images, + 'hotel' => BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $uid), + ]; + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserProductPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserProductPreviewRenderer.php new file mode 100644 index 0000000..24ae891 --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserProductPreviewRenderer.php @@ -0,0 +1,24 @@ +data['tx_eptheme_context_product']; + $hotelUid = $this->data['tx_eptheme_context_hotel']; + $images = $this->getImages($productUid, 'tx_epproducts_domain_model_product', 'teaser_images'); + $product = BackendUtility::getRecord('tx_epproducts_domain_model_product', $productUid); + $hotel = $hotelUid ? BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $hotelUid) : null; + return [ + 'images' => $images, + 'product' => $product, + 'hotel' => $hotel, + ]; + } +} diff --git a/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserRegionPreviewRenderer.php b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserRegionPreviewRenderer.php new file mode 100644 index 0000000..9d221b1 --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserRegionPreviewRenderer.php @@ -0,0 +1,20 @@ +data['tx_eptheme_context_region']; + $images = $this->getImages($uid, 'tx_epproducts_domain_model_region', 'teaser_images'); + return [ + 'images' => $images, + 'region' => BackendUtility::getRecord('tx_epproducts_domain_model_region', $uid), + ]; + } +} diff --git a/packages/ep_theme/Classes/Hooks/Tcemain.php b/packages/ep_theme/Classes/Hooks/Tcemain.php new file mode 100644 index 0000000..3a5ba32 --- /dev/null +++ b/packages/ep_theme/Classes/Hooks/Tcemain.php @@ -0,0 +1,62 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\DataHandling\DataHandler; + +class Tcemain +{ + /** + * @param array $fields + * @param string $table + * @param int $id + * @param DataHandler $dataHandler + */ + public function processDatamap_preProcessFieldArray(array &$fields, $table, $id, DataHandler $dataHandler) + { + // Get country code from associated country entity and store in page record + if ($table === 'pages') { + $countryUid = (int) $fields['tx_eptheme_context_country']; + if ($countryUid === 0) { + $fields['tx_eptheme_context_country_code'] = ''; + } else { + $country = BackendUtility::getRecord('tx_epproducts_domain_model_country', $countryUid); + if ($country !== null) { + $fields['tx_eptheme_context_country_code'] = $country['code']; + } + } + } + // Force unused colPos for content elements appended to news records + if ($table === 'tt_content') { + if (isset($dataHandler->datamap['tx_news_domain_model_news'])) { + $fields['colPos'] = 1848; + } + } + } +} diff --git a/packages/ep_theme/Classes/MetaTag/OpenGraphMetaTagManager.php b/packages/ep_theme/Classes/MetaTag/OpenGraphMetaTagManager.php new file mode 100644 index 0000000..52bc792 --- /dev/null +++ b/packages/ep_theme/Classes/MetaTag/OpenGraphMetaTagManager.php @@ -0,0 +1,69 @@ +properties)) { + return parent::renderAllProperties(); + } + /** @var FileRepository $fileRepository */ + $fileRepository = GeneralUtility::makeInstance(FileRepository::class); + + $pageUid = $this->getPageWithImage(); + + if (null === $pageUid) { + return parent::renderAllProperties(); + } + + $images = $fileRepository->findByRelation('pages', 'og_image', $pageUid); + + /** @var ImageService $imageService */ + $imageService = GeneralUtility::makeInstance(ImageService::class); + + foreach ($images as $image) { + $processedImage = $imageService->applyProcessingInstructions( + $image, + [ + 'width' => '1200' + ] + ); + $this->addProperty( + 'og:image', + $imageService->getImageUri($processedImage, true), + [ + 'url' => $imageService->getImageUri($processedImage, true), + 'width' => $processedImage->getProperty('width'), + 'height' => $processedImage->getProperty('height'), + 'type' => $processedImage->getMimeType(), + ] + ); + } + + return parent::renderAllProperties(); + } + + /** + * @return null|int + */ + protected function getPageWithImage() + { + $rootLineUtility = new RootlineUtility($GLOBALS['TSFE']->id); + $rootLine = $rootLineUtility->get(); + + foreach ($rootLine as $page) { + if ($page['og_image'] > 0) { + return $page['uid']; + } + } + + return null; + } +} \ No newline at end of file diff --git a/packages/ep_theme/Classes/Middleware/ReferrerMiddleware.php b/packages/ep_theme/Classes/Middleware/ReferrerMiddleware.php new file mode 100644 index 0000000..6ad3e73 --- /dev/null +++ b/packages/ep_theme/Classes/Middleware/ReferrerMiddleware.php @@ -0,0 +1,43 @@ +getQueryParams(); + + if (!array_key_exists('ref', $queryParams)) { + return $handler->handle($request); + } + + $ref = $queryParams['ref']; + + if (false === is_numeric($ref)) { + return $handler->handle($request); + } + + /** @var SiteFinder $siteFinder */ + $siteFinder = GeneralUtility::makeInstance(SiteFinder::class); + + try { + $site = $siteFinder->getSiteByPageId((int) $ref); + $url = $site->getRouter()->generateUri($ref); + $GLOBALS['TSFE']->fe_user->setKey('ses', 'referrer', $url); + $GLOBALS['TSFE']->fe_user->storeSessionData(); + } + catch (SiteNotFoundException $exception) { + } + + return $handler->handle($request); + } +} diff --git a/packages/ep_theme/Classes/Service/EmailService.php b/packages/ep_theme/Classes/Service/EmailService.php new file mode 100644 index 0000000..180804e --- /dev/null +++ b/packages/ep_theme/Classes/Service/EmailService.php @@ -0,0 +1,102 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use Symfony\Component\Mime\Address; +use TYPO3\CMS\Core\Mail\FluidEmail; +use TYPO3\CMS\Core\Mail\Mailer; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; + +class EmailService implements SingletonInterface +{ + /** + * @var array + */ + protected $settings; + + public function __construct(ConfigurationManagerInterface $manager) + { + $settings = GeneralUtility::removeDotsFromTS( + $manager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + $this->settings = $settings['plugin']['tx_eptheme']; + } + + /** + * @param string $toEmail + * @param string $toName + * @param string $subject + * @param string $templateName + * @param array $variables + */ + public function send($toEmail, $toName, $subject, $templateName, array $variables = []) + { + /** @var Mailer $mailer */ + $mailer = GeneralUtility::makeInstance(Mailer::class); + $fromEmail = $this->settings['settings']['contactFormToEmail']; + $fromName = $this->settings['settings']['contactFormToName']; + $enableAutoreply = (bool)$this->settings['settings']['enableContactFormAutoreply']; + $autoreplySubject = $this->settings['settings']['contactFormAutoreplySubject']; + + /** @var FluidEmail $message */ + $message = GeneralUtility::makeInstance(FluidEmail::class); + $message + ->to(new Address($toEmail, $toName)) + ->from(new Address($fromEmail, $fromName)) + ->format('html') + ->subject($subject) + ->setTemplate($templateName) + ->assignMultiple($variables) + ; + + $mailer->send($message); + + if ($enableAutoreply) { + $contactForm = $variables['contactForm']; + $toEmail = $contactForm->getEmail(); + $toName = $contactForm->getName(); + $templateName = 'Autoreply'; + + /** @var FluidEmail $message */ + $message = GeneralUtility::makeInstance(FluidEmail::class); + $message + ->to(new Address($toEmail, $toName)) + ->from(new Address($fromEmail, $fromName)) + ->format('html') + ->subject($autoreplySubject) + ->setTemplate($templateName) + ->assignMultiple($variables) + ; + + $mailer->send($message); + } + + return true; + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/ArgumentPrefixViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/ArgumentPrefixViewHelper.php new file mode 100644 index 0000000..d291df7 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/ArgumentPrefixViewHelper.php @@ -0,0 +1,70 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class ArgumentPrefixViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('pluginName', 'string', 'Plugin name'); + $this->registerArgument('extensionName', 'string', 'Extension name'); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $request = $renderingContext->getControllerContext()->getRequest(); + $pluginName = $arguments['pluginName']; + if ($pluginName === null) { + $pluginName = $request->getPluginName(); + } + $extensionName = $arguments['extensionName']; + if ($extensionName === null) { + $extensionName = $request->getControllerExtensionName(); + } + return strtolower(implode('_', ['tx', $extensionName, $pluginName])); + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/Calendar/ContingentViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Calendar/ContingentViewHelper.php new file mode 100644 index 0000000..bf3a38e --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/Calendar/ContingentViewHelper.php @@ -0,0 +1,201 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use DateInterval; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class ContingentViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + /** + * @var bool + */ + protected $escapeOutput = false; + + private const LEVEL_GREEN = 1; + private const LEVEL_YELLOW = 2; + private const LEVEL_RED = 3; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('day', \DateTimeInterface::class, 'The day', true); + $this->registerArgument('calendar', 'array', 'The calendar payload', true); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $day = $arguments['day']; + $calendar = $arguments['calendar']; + $now = new \DateTimeImmutable('now'); + $dayDate = new \DateTimeImmutable($day->format('Y-m-d H:i:s'), $day->getTimezone()); + $dayDate = $dayDate->setTime(0, 0, 0); + + if (!empty($calendar['error'])) { + $class = [ 'text-zinc-400' ]; + return static::getHtml($class, $dayDate->format('d')); + } + + if ($now > $dayDate) { + $class = [ 'text-zinc-400' ]; + return static::getHtml($class, $dayDate->format('d')); + } + $statusMap = static::getStatusMap($calendar); + $status = static::normalizeStatus($statusMap[$dayDate->format('Y-m-d')] ?? 'OK'); + $previousStatus = static::normalizeStatus($statusMap[$dayDate->sub(new DateInterval('P1D'))->format('Y-m-d')] ?? 'OK'); + $level = static::getOccupancyLevel($status); + $previousLevel = static::getOccupancyLevel($previousStatus); + + if ($previousLevel !== $level) { + $class = static::getTransientLabelClasses($previousStatus, $status); + } else { + $class = static::getLabelClasses($status); + } + + return static::getHtml($class, $dayDate->format('d')); + } + + /** + * @return array + */ + protected static function getLabelClasses(string $status): array + { + return [ + 'block p-1 leading-none text-sm text-white', + static::getStatusClass($status), + ]; + } + + /** + * @return array + */ + protected static function getTransientLabelClasses(string $statusFrom, ?string $statusTo = null): array + { + $classes = [ 'block p-1 leading-none text-sm text-white' ]; + $class = static::getStatusClass($statusFrom); + if ($statusTo !== null) { + $class .= '-to-' . static::getStatusSuffix($statusTo); + } + $classes[] = $class; + return $classes; + } + + /** + * @return int + */ + protected static function getOccupancyLevel(string $status): int + { + if ($status === 'BLOCKED') { + return static::LEVEL_RED; + } + if ($status === 'ON_REQUEST') { + return static::LEVEL_YELLOW; + } + return static::LEVEL_GREEN; + } + + /** + * @return array + */ + protected static function getStatusMap(array $calendar): array + { + $statusMap = []; + foreach (($calendar['data'] ?? []) as $row) { + if (!isset($row['date'])) { + continue; + } + $statusMap[$row['date']] = static::normalizeStatus((string)($row['status'] ?? 'OK')); + } + + return $statusMap; + } + + protected static function normalizeStatus(string $status): string + { + $status = strtoupper(trim($status)); + if ($status === 'ONREQUEST') { + return 'ON_REQUEST'; + } + + if (in_array($status, [ 'OK', 'ON_REQUEST', 'BLOCKED' ], true)) { + return $status; + } + + return 'OK'; + } + + protected static function getStatusClass(string $status): string + { + if ($status === 'BLOCKED') { + return 'availability-none'; + } + if ($status === 'ON_REQUEST') { + return 'availability-partial'; + } + + return 'availability-full'; + } + + protected static function getStatusSuffix(string $status): string + { + if ($status === 'BLOCKED') { + return 'none'; + } + if ($status === 'ON_REQUEST') { + return 'partial'; + } + + return 'full'; + } + + /** + * @param array $class + * @param string $label + * @return string + */ + protected static function getHtml(array $class, $label) + { + $cssClass = ' class="' . implode(' ', $class) . '"'; + return sprintf('%s', $cssClass, $label); + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/ComfortCategoryViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/ComfortCategoryViewHelper.php new file mode 100644 index 0000000..d135dcb --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/ComfortCategoryViewHelper.php @@ -0,0 +1,92 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class ComfortCategoryViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('hotel', Hotel::class, 'The hotel'); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + /** @var Hotel $hotel */ + $hotel = $arguments['hotel']; + + $flag = '%s'; + + switch ($hotel->getCategory()) + { + case Hotel::CATEGORY_STANDARD: + $icon = 'S'; + $tooltip = 'Kategorie: Standard'; + break; + case Hotel::CATEGORY_BASIC: + $icon = 'B'; + $tooltip = 'Kategorie: Basic'; + break; + case Hotel::CATEGORY_COMFORT: + $icon = 'C'; + $tooltip = 'Kategorie: Komfort'; + break; + case Hotel::CATEGORY_DELUXE: + $icon = 'D'; + $tooltip = 'Kategorie: Deluxe'; + break; + case Hotel::CATEGORY_SUPER_DELUXE: + $icon = 'SD'; + $tooltip = 'Kategorie: Superdeluxe'; + break; + default: + return ''; + } + + return sprintf($flag, $tooltip, $icon); + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/ContainerViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/ContainerViewHelper.php new file mode 100644 index 0000000..1977186 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/ContainerViewHelper.php @@ -0,0 +1,117 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class ContainerViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + /** + * @var int[] + */ + protected static $scale = [ + 'default' => 0, + 'small' => 8, + 'large' => 16, + ]; + + /** + * @var bool + */ + protected $escapeOutput = false; + + /** + * @var array + */ + protected static $columnsWithoutContainers = [ 0, 1, 3 ]; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('content', 'string', 'The content'); + $this->registerArgument('cssClasses', 'string', 'Additional CSS classes'); + $this->registerArgument('rteContent', 'boolean', 'Is RTE content', false, false); + $this->registerArgument('data', 'Array', 'The record data', true); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) { + $content = $renderChildrenClosure(); + + if ($content === null) { + $content = $arguments['content']; + } + + $classes = []; + if (\in_array($arguments['data']['colPos'], static::$columnsWithoutContainers, false)) { + $classes[] = 'container'; + } + + if ($arguments['cssClasses'] !== null) { + $classes[] = $arguments['cssClasses']; + } + + if (!empty($arguments['data']['space_before_class'])) { + $scaleTop = static::$scale[$arguments['data']['space_before_class']]; + $classes[] = sprintf('pt-%d', $scaleTop); + } + + if (!empty($arguments['data']['space_after_class'])) { + $scaleBottom = static::$scale[$arguments['data']['space_after_class']]; + $classes[] = sprintf('pb-%d', $scaleBottom); + } + + if ('default' !== $arguments['data']['frame_class']) { + $classes[] = $arguments['data']['frame_class']; + } + + if (count($classes) > 0) { + return sprintf('
%s
', implode(' ', $classes), $arguments['rteContent'] ? ' data-rte-content' : '', $content); + } + + if ($arguments['rteContent']) { + return sprintf('
%s
', $content); + } + + return $content; + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/ContextClassesViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/ContextClassesViewHelper.php new file mode 100644 index 0000000..bd6b82a --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/ContextClassesViewHelper.php @@ -0,0 +1,71 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class ContextClassesViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('context', 'Array', 'The context', true); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $classes = []; + if (isset($arguments['context']['country'])) { + $classes[] = mb_strtolower($arguments['context']['country']['name']); + } + if (isset($arguments['context']['region'])) { + $classes[] = mb_strtolower($arguments['context']['region']['name']); + } + + $classesString = implode(' ', $classes); + + $classesString = str_replace(['ä', 'ö', 'ü', 'ß'], ['ae', 'oe', 'ue', 'ss'], $classesString); + + return $classesString; + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/DateRangeViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/DateRangeViewHelper.php new file mode 100644 index 0000000..94ce30a --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/DateRangeViewHelper.php @@ -0,0 +1,67 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Utility\DateUtility; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class DateRangeViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('dateFrom', 'string', 'Date range start'); + $this->registerArgument('dateTo', 'string', 'Date range end'); + $this->registerArgument('includeLabel', 'Bool', 'Whether to include a label', false, true); + $this->registerArgument('format', 'String', 'The date format', false, 'd.m.Y'); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) { + return DateUtility::formatDateRange( + $arguments['dateFrom'], + $arguments['dateTo'], + $arguments['includeLabel'], + $arguments['format'] + ); + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/FilterSettingsEncoderViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/FilterSettingsEncoderViewHelper.php new file mode 100644 index 0000000..aba18bc --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/FilterSettingsEncoderViewHelper.php @@ -0,0 +1,75 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\FilterSettingsEncoder; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class FilterSettingsEncoderViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + /** + * @var bool + */ + protected $escapeOutput = false; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('argument', 'string', 'The argument string to encode'); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $argument = $renderChildrenClosure(); + if ($argument === null) { + $argument = $arguments['argument']; + } + if ($argument === null) { + return ''; + } + + $encoder = new FilterSettingsEncoder(); + + return $encoder->encode($argument); + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/GmapsUrlViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/GmapsUrlViewHelper.php new file mode 100644 index 0000000..3d4d1dd --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/GmapsUrlViewHelper.php @@ -0,0 +1,83 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Region; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class GmapsUrlViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + /** + * @var bool + */ + protected $escapeOutput = false; + + const URLBASE = 'http://maps.google.com/maps?q=%s,%s&t=%s&z=%d'; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('hotel', Hotel::class, 'The hotel'); + $this->registerArgument('region', Region::class, 'The region'); + $this->registerArgument('type', 'string', 'The map type', false, 'p'); + $this->registerArgument('zoom', 'int', 'The zoom level', false, 10); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $hotel = $arguments['hotel']; + $region = $arguments['region']; + if ($hotel !== null && $hotel->getLatitude() && $hotel->getLongitude()) { + $source = $hotel; + } elseif ($hotel !== null && $hotel->getRegion()->getLatitude() && $hotel->getRegion()->getLongitude()) { + $source = $hotel->getRegion(); + } elseif ($region !== null) { + $source = $region; + } else { + return 'http://www.google.de/maps'; + } + return sprintf(static::URLBASE, $source->getLatitude(), $source->getLongitude(), $arguments['type'], $arguments['zoom']); + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/HotelCategoriesViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/HotelCategoriesViewHelper.php new file mode 100644 index 0000000..7bcf4f2 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/HotelCategoriesViewHelper.php @@ -0,0 +1,72 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class HotelCategoriesViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('categories', 'array', 'The categories', true); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $categories = $arguments['categories']; + + switch (count($categories)) { + case 1: + $label = reset($categories); + break; + default: + $categoryMinLabel = array_shift($categories); + $categoryMaxLabel = array_pop($categories); + $label = $categoryMinLabel . ' - ' . $categoryMaxLabel; + } + + return $label; + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/HotelCategoryViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/HotelCategoryViewHelper.php new file mode 100644 index 0000000..b6fa565 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/HotelCategoryViewHelper.php @@ -0,0 +1,73 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class HotelCategoryViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + /** + * @var array + */ + static protected $categoryLabels = [ + Hotel::CATEGORY_STANDARD => 'Standard', + Hotel::CATEGORY_COMFORT => 'Comfort', + Hotel::CATEGORY_DELUXE => 'Deluxe', + Hotel::CATEGORY_SUPER_DELUXE => 'Super Deluxe', + ]; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('category', 'int', 'The category id', true); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $category = $arguments['category']; + + return static::$categoryLabels[$category]; + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/IconViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/IconViewHelper.php new file mode 100644 index 0000000..0bcb8ae --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/IconViewHelper.php @@ -0,0 +1,41 @@ +registerUniversalTagAttributes(); + $this->registerArgument('icon', 'string', 'Icon name', true); + } + + public function render(): string + { + $imageService = self::getImageService(); + $sprite = $imageService->getImage('EXT:ep_theme/Resources/Public/images/icons_sprite.svg', null, false); + $imageUrl = $sprite->getPublicUrl(); + + $content = sprintf('', $imageUrl, $this->arguments['icon']); + $this->tag->setContent($content); + $this->tag->addAttributes(['aria-hidden' => 'true']); + + return $this->tag->render(); + } + + protected static function getImageService() + { + return GeneralUtility::makeInstance(ImageService::class); + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/MyEpUrlViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/MyEpUrlViewHelper.php new file mode 100644 index 0000000..bd6a874 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/MyEpUrlViewHelper.php @@ -0,0 +1,28 @@ +resolve(); + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/NightsOptionsViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/NightsOptionsViewHelper.php new file mode 100644 index 0000000..3df535c --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/NightsOptionsViewHelper.php @@ -0,0 +1,81 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class NightsOptionsViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('options', 'array', 'The options'); + $this->registerArgument('daytrip', 'bool', 'Is daytrip or not', false, false); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + if ($arguments['daytrip']) { + return 'Tageweise buchbar'; + } + $options = $arguments['options']; + if ($options === null) { + return ''; + } + sort($options); + $optionsCount = count($options); + $lastOption = array_pop($options); + $output = ''; + if ($optionsCount > 2) { + $output .= implode(', ' , $options) . ' oder ' . $lastOption; + } elseif ($optionsCount === 2) { + $output .= reset($options) . ' oder ' . $lastOption; + } else { + $output = $lastOption; + } + $output .= (int) $lastOption > 1 ? ' Nächte' : ' Nacht'; + + return $output; + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/PriceRangeViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/PriceRangeViewHelper.php new file mode 100644 index 0000000..c38cede --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/PriceRangeViewHelper.php @@ -0,0 +1,66 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class PriceRangeViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('min', 'mixed', 'Price range start'); + $this->registerArgument('max', 'mixed', 'Price range end'); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) { + if (1 === (int) $arguments['min']) { + return sprintf('bis %d €', $arguments['max']); + } + if (999 === (int) $arguments['max']) { + return sprintf('ab %d €', $arguments['min']); + } + + return sprintf('%d bis %d €', $arguments['min'], $arguments['max']); + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/PriceViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/PriceViewHelper.php new file mode 100644 index 0000000..10b98bc --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/PriceViewHelper.php @@ -0,0 +1,45 @@ + ep:price()} € + */ +class PriceViewHelper extends AbstractViewHelper +{ + use CompileWithContentArgumentAndRenderStatic; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('value', 'mixed', 'Amount in euros'); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return string + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) { + $amount = round((float) $renderChildrenClosure(), 2); + $decimals = floor($amount) === $amount ? 0 : 2; + + return number_format($amount, $decimals, ',', ''); + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/RatingViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/RatingViewHelper.php new file mode 100644 index 0000000..b1df2f0 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/RatingViewHelper.php @@ -0,0 +1,86 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Product; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class RatingViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + /** + * @var bool + */ + protected $escapeOutput = false; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('product', Product::class, 'The product', true); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $product = $renderChildrenClosure(); + if ($product === null) { + $product = $arguments['product']; + } + $ratingAverage = $product->getRatingAverage(); + $ratingVotesCount = $product->getRatingVotesCount(); + $fullStars = floor($ratingAverage); + $halfStar = ($ratingAverage - $fullStars) > 0; + $ratingAverageLabel = number_format($ratingAverage, 1, ',', '.'); + + $content = ''; + for ($i = 1; $i <= $fullStars; $i++) + { + $content .= ''; + } + if ($halfStar) { + $content .= ''; + } + $content .= sprintf('%s Sterne (%d)', $ratingAverageLabel, $ratingVotesCount); + + return $content; + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/Stimulus/AbstractStimulusViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Stimulus/AbstractStimulusViewHelper.php new file mode 100644 index 0000000..27d0c00 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/Stimulus/AbstractStimulusViewHelper.php @@ -0,0 +1,41 @@ + + */ +class AbstractStimulusViewHelper extends AbstractViewHelper +{ + /** + * This method is taken from symfony/webpack-encore-bundle + * + * Normalize a Stimulus controller name into its HTML equivalent (no special character and / becomes --). + * + * @see https://stimulus.hotwire.dev/reference/controllers + */ + protected static function normalizeControllerName(string $str): string + { + return preg_replace('/^@/', '', str_replace('_', '-', str_replace('/', '--', $str))); + } + + /** + * This method is taken from symfony/webpack-encore-bundle + * + * Normalize a Stimulus Value API key into its HTML equivalent ("kebab case"). + * Backport features from symfony/string. + * + * @see https://stimulus.hotwire.dev/reference/values + */ + protected static function normalizeKeyName(string $str): string + { + // Adapted from ByteString::camel + $str = ucfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $str)))); + + // Adapted from ByteString::snake + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1-\2', $str)); + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/Stimulus/ActionViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Stimulus/ActionViewHelper.php new file mode 100644 index 0000000..6b9108f --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/Stimulus/ActionViewHelper.php @@ -0,0 +1,88 @@ + + */ +class ActionViewHelper extends AbstractStimulusViewHelper +{ + use CompileWithRenderStatic; + + protected $escapeOutput = false; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('controller', 'mixed', 'Controller name(s)', true); + $this->registerArgument('action', 'string', 'Action name', false); + $this->registerArgument('event', 'string', 'Event name', false); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return string + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $dataOrControllerName = $arguments['controller']; + $actionName = $arguments['action']; + $eventName = $arguments['event']; + + if (\is_string($dataOrControllerName)) { + $data = [$dataOrControllerName => null === $eventName ? [[$actionName]] : [[$eventName => $actionName]]]; + } else { + if ($actionName || $eventName) { + throw new \InvalidArgumentException('You cannot pass a string to the second or third argument while passing an array to the first argument of stimulus_action(): check the documentation.'); + } + + $data = $dataOrControllerName; + + if (!$data) { + return ''; + } + } + + $actions = []; + + foreach ($data as $controllerName => $controllerActions) { + $controllerName = static::normalizeControllerName($controllerName); + + if (\is_string($controllerActions)) { + $controllerActions = [[$controllerActions]]; + } + + foreach ($controllerActions as $possibleEventName => $controllerAction) { + if (\is_string($possibleEventName) && \is_string($controllerAction)) { + $controllerAction = [$possibleEventName => $controllerAction]; + } elseif (\is_string($controllerAction)) { + $controllerAction = [$controllerAction]; + } + + foreach ($controllerAction as $eventName => $actionName) { + $action = $controllerName.'#'.htmlspecialchars($actionName, ENT_QUOTES); + + if (\is_string($eventName)) { + $action = $eventName.'->'.$action; + } + + $actions[] = $action; + } + } + } + + return 'data-action="'.implode(' ', $actions).'"'; + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/Stimulus/ControllerViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Stimulus/ControllerViewHelper.php new file mode 100644 index 0000000..8f517a1 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/Stimulus/ControllerViewHelper.php @@ -0,0 +1,81 @@ + + */ +class ControllerViewHelper extends AbstractStimulusViewHelper +{ + use CompileWithRenderStatic; + + protected $escapeOutput = false; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('controller', 'mixed', 'Controller name(s)', true); + $this->registerArgument('values', 'array', 'Controller values', false, []); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return string + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $dataOrControllerName = $arguments['controller']; + $controllerValues = $arguments['values']; + + if (\is_string($dataOrControllerName)) { + $data = [$dataOrControllerName => $controllerValues]; + } else { + if ($controllerValues) { + throw new \InvalidArgumentException('You cannot pass an array to the first and second argument of stimulus_controller(): check the documentation.'); + } + + $data = $dataOrControllerName; + + if (!$data) { + return ''; + } + } + + $controllers = []; + $values = []; + + foreach ($data as $controllerName => $controllerValues) { + $controllerName = static::normalizeControllerName($controllerName); + $controllers[] = $controllerName; + + foreach ($controllerValues as $key => $value) { + if (!is_scalar($value)) { + $value = json_encode($value); + } + + if (\is_bool($value)) { + $value = $value ? 'true' : 'false'; + } + + $key = static::normalizeKeyName($key); + $value = htmlspecialchars($value, ENT_QUOTES); + + $values[] = 'data-'.$controllerName.'-'.$key.'-value="'.$value.'"'; + } + } + + return rtrim('data-controller="'.implode(' ', $controllers).'" '.implode(' ', $values)); + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/Stimulus/TargetViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Stimulus/TargetViewHelper.php new file mode 100644 index 0000000..cbaaeb9 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/Stimulus/TargetViewHelper.php @@ -0,0 +1,68 @@ + + */ +class TargetViewHelper extends AbstractStimulusViewHelper +{ + use CompileWithRenderStatic; + + protected $escapeOutput = false; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('controller', 'mixed', 'Controller name(s)', true); + $this->registerArgument('targets', 'string', 'Target names', false); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return string + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $dataOrControllerName = $arguments['controller']; + $targetNames = $arguments['target']; + + if (\is_string($dataOrControllerName)) { + $data = [$dataOrControllerName => $targetNames]; + } else { + if ($targetNames) { + throw new \InvalidArgumentException('You cannot pass a string to the second argument while passing an array to the first argument of stimulus_target(): check the documentation.'); + } + + $data = $dataOrControllerName; + + if (!$data) { + return ''; + } + } + + $targets = []; + + foreach ($data as $controllerName => $targetNames) { + $controllerName = static::normalizeControllerName($controllerName); + + $targets['data-'.$controllerName.'-target'] = htmlspecialchars($targetNames, ENT_QUOTES); + } + + return implode(' ', array_map(static function (string $attribute, string $value): string { + return $attribute.'="'.$value.'"'; + }, array_keys($targets), $targets)); + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/ThemeClassesViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/ThemeClassesViewHelper.php new file mode 100644 index 0000000..b61219d --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/ThemeClassesViewHelper.php @@ -0,0 +1,66 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class ThemeClassesViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('key', 'string', 'The theme key', false, 'ep'); + $this->registerArgument('classes', 'array', 'The classes to apply per theme key', true); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $themeKey = $arguments['key']; + + if (!array_key_exists($themeKey, $arguments['classes'])) { + return ''; + } + + return $arguments['classes'][$themeKey]; + } +} diff --git a/packages/ep_theme/Classes/ViewHelpers/Uri/AjaxViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Uri/AjaxViewHelper.php new file mode 100644 index 0000000..4b9aa8c --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/Uri/AjaxViewHelper.php @@ -0,0 +1,89 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Utility\RootlineUtility; +use TYPO3\CMS\Fluid\ViewHelpers\Uri\ActionViewHelper; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; + +class AjaxViewHelper extends ActionViewHelper +{ + + const PAGE_TYPE_JSON = 1701; + const PAGE_TYPE_HTML = 1702; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->overrideArgument('extensionName', 'string', 'Target extension name', false, 'epproducts'); + $this->overrideArgument('pluginName', 'string', 'Target plugin name', false, 'Ajax'); + $this->overrideArgument('format', 'string', 'The requested format', false, 'json'); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return string + */ + public static function renderStatic + ( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) { + // Determine page type from format argument + if (strtolower(trim($arguments['format'])) === 'json') { + $arguments['pageType'] = self::PAGE_TYPE_JSON; + } else { + $arguments['pageType'] = self::PAGE_TYPE_HTML; + } + + // Reset argument to keep it out of resulting url + unset($arguments['format']); + + // Use root page uid as target unless provided + if ($arguments['pageUid'] === null) { + $rootlineUtility = new RootlineUtility($GLOBALS['TSFE']->id); + $rootline = $rootlineUtility->get(); + $rootPage = array_pop($rootline); + $arguments['pageUid'] = $rootPage['uid']; + } + + // Force absolute urls + $arguments['absolute'] = true; + + return parent::renderStatic( + $arguments, + $renderChildrenClosure, + $renderingContext + ); + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/Uri/BookingViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Uri/BookingViewHelper.php new file mode 100644 index 0000000..a4fd419 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/Uri/BookingViewHelper.php @@ -0,0 +1,92 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Utility\BookingUrlUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class BookingViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + /** + * @var array + */ + protected static $settings = []; + + /** + * @param ConfigurationManagerInterface $configurationManager + */ + public function __construct(ConfigurationManagerInterface $configurationManager) + { + $settings = GeneralUtility::removeDotsFromTS( + $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + static::$settings = $settings['plugin']['tx_theme']; + } + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('bookingId', 'string', 'The booking id', true); + $this->registerArgument('hotelId', 'string', 'The hotel id', true); + $this->registerArgument('template', 'string', 'The booking template'); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $template = $arguments['template']; + + if ($template === null) { + $template = static::$settings['bpnBookungUrlTemplateCode']; + } + + $options = [ + 'dateId' => $arguments['bookingId'], + 'hotelId' => $arguments['hotelId'], + 'theme' => $template, + ]; + return BookingUrlUtility::generateUrl($options); + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/Uri/ExternalImageViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Uri/ExternalImageViewHelper.php new file mode 100644 index 0000000..b4c02ac --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/Uri/ExternalImageViewHelper.php @@ -0,0 +1,71 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class ExternalImageViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + /** + * @var bool + */ + protected $escapeOutput = false; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('url', 'string', 'The url'); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + $url = $renderChildrenClosure(); + if ($url === null) { + $url = $arguments['url']; + } + $encryptionKey = $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']; + $token = sha1($url . $encryptionKey); + return 'https://www.ep-reisen.de/typo3conf/ext/ep_theme/extimg.php?url=' . urlencode($url) . '&token=' . $token; + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/Uri/ProductViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Uri/ProductViewHelper.php new file mode 100644 index 0000000..0d24fd3 --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/Uri/ProductViewHelper.php @@ -0,0 +1,103 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class ProductViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('product', 'mixed', 'The product'); + $this->registerArgument('hotel', 'array', 'The hotel'); + $this->registerArgument('forceHotelUid', 'bool', 'Whether to force link a hotel'); + $this->registerArgument('dateFrom', 'string', 'Date to limit table on detail page by (Y-m-d)'); + $this->registerArgument('dateTo', 'string', 'Date to limit table on detail page by (Y-m-d)'); + $this->registerArgument('ref', 'string', 'Referring URL'); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) { + $product = $arguments['product']; + $hotel = $arguments['hotel']; + + if ($arguments['forceHotelUid']) { + // Product detail page with forced hotel + $linkArguments = [ + 'product' => is_array($product) ? $product['uid'] : $product, + 'hotel' => is_array($hotel) ? $hotel['uid'] : $hotel, + ]; + } elseif ($arguments['dateFrom'] || $arguments['dateTo']) { + // Product detail page with forced date range + $linkArguments = [ + 'dateFrom' => $arguments['dateFrom'], + 'dateTo' => $arguments['dateTo'], + ]; + } else { + // Default product detail page by product uid + $linkArguments = [ + 'product' => is_array($product) ? $product['uid'] : $product, + ]; + } + + $targetPageUid = is_array($product) ? (int)$product['detailPage'] : $product->getDetailPage(); + + $uri = $renderingContext + ->getControllerContext() + ->getUriBuilder() + ->reset() + ->setTargetPageUid($targetPageUid) + ->uriFor('detail', $linkArguments, 'Product', 'epproducts', 'product_detail') + ; + + if ($arguments['ref']) { + $referringPageUri = urlencode($arguments['ref']); + $hasQuery = null !== parse_url($uri, PHP_URL_QUERY); + $separator = $hasQuery ? '&' : '?'; + $uri .= $separator . 'ref=' . $referringPageUri; + } + + return $uri; + } + +} diff --git a/packages/ep_theme/Classes/ViewHelpers/Uri/VideoViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Uri/VideoViewHelper.php new file mode 100644 index 0000000..5811b0c --- /dev/null +++ b/packages/ep_theme/Classes/ViewHelpers/Uri/VideoViewHelper.php @@ -0,0 +1,75 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; +use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; +use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; + +class VideoViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + /** + * @var bool + */ + protected $escapeOutput = false; + + public function initializeArguments() + { + parent::initializeArguments(); + + $this->registerArgument('value', 'string', 'The video url', true); + $this->registerArgument('layout', 'int', 'The layout', false, 1); + } + + /** + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) + { + // YouTube + if ((int) $arguments['layout'] === 0) { + return $arguments['value'] . '?rel=0&controls=0&showinfo=0'; + } + // Vimeo + if ((int) $arguments['layout'] === 1) { + return sprintf('https://player.vimeo.com/video/%d?html5=1', $arguments['value']); + } + + return ''; + } + +} diff --git a/packages/ep_theme/Configuration/.htaccess b/packages/ep_theme/Configuration/.htaccess new file mode 100755 index 0000000..3418e55 --- /dev/null +++ b/packages/ep_theme/Configuration/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/packages/ep_theme/Configuration/Extbase/Persistence/Classes.php b/packages/ep_theme/Configuration/Extbase/Persistence/Classes.php new file mode 100644 index 0000000..347a25e --- /dev/null +++ b/packages/ep_theme/Configuration/Extbase/Persistence/Classes.php @@ -0,0 +1,9 @@ + [ + 'tableName' => 'pages', + ], +]; diff --git a/packages/ep_theme/Configuration/FlexForms/flexform_disturber.xml b/packages/ep_theme/Configuration/FlexForms/flexform_disturber.xml new file mode 100644 index 0000000..2fdfd63 --- /dev/null +++ b/packages/ep_theme/Configuration/FlexForms/flexform_disturber.xml @@ -0,0 +1,64 @@ + + + 1 + + + + + + Konfiguration + + array + + +
1
+ array + + + Button + array + + + + + 1 + + + input + trim + 30 + + <_PADDING type="integer">2 + + popup + Link + EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif + + wizard_link + + wizard + + + height=300,width=500,status=0,menubar=0,scrollbars=1 + + + + + + + + +
+
+
+
+
+
diff --git a/packages/ep_theme/Configuration/RTE/Default.yaml b/packages/ep_theme/Configuration/RTE/Default.yaml new file mode 100644 index 0000000..5f2866c --- /dev/null +++ b/packages/ep_theme/Configuration/RTE/Default.yaml @@ -0,0 +1,41 @@ +imports: + - { resource: "EXT:rte_ckeditor/Configuration/RTE/Processing.yaml" } + - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Base.yaml" } + - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Plugins.yaml" } + +editor: + config: + disableNativeSpellChecker: false + contentsCss: + - "EXT:ep_theme/Resources/Public/css/rte.css?v=2022.07-01" + + stylesSet: + - { name: "Button", element: "a", attributes: { 'class': 'rte-button' } } + - { name: "Button volle Breite", element: "a", attributes: { 'class': 'rte-button--full' } } + + toolbarGroups: + - { name: styles, groups: [ format, styles ] } + - { name: basicstyles, groups: [ basicstyles ] } + - { name: paragraph, groups: [ list] } + - "/" + - { name: links, groups: [ links ] } + - { name: clipboard, groups: [ clipboard, cleanup, undo ] } + - { name: editing, groups: [ spellchecker ] } + - { name: insert, groups: [ insert ] } + - { name: tools, groups: [ table, specialchar ] } + - { name: document, groups: [ mode ] } + + format_tags: "p;h1;h2;h3;h4" + + removePlugins: + - image + + removeButtons: + - Anchor + - Underline + - Strike + +buttons: + link: + queryParametersSelector: + enabled: 1 diff --git a/packages/ep_theme/Configuration/RTE/Minimal.yaml b/packages/ep_theme/Configuration/RTE/Minimal.yaml new file mode 100644 index 0000000..27a1054 --- /dev/null +++ b/packages/ep_theme/Configuration/RTE/Minimal.yaml @@ -0,0 +1,37 @@ +imports: + - { resource: "EXT:rte_ckeditor/Configuration/RTE/Processing.yaml" } + - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Base.yaml" } + - { resource: "EXT:rte_ckeditor/Configuration/RTE/Editor/Plugins.yaml" } + +editor: + config: + height: 100 + disableNativeSpellChecker: false + contentsCss: + - "EXT:ep_theme/Resources/Public/css/rte.css?v=2020.02-01" + + stylesSet: + - { name: "Button", element: "a", attributes: { 'class': 'rte-button' } } + - { name: "Button volle Breite", element: "a", attributes: { 'class': 'rte-button--full' } } + + toolbarGroups: + - { name: paragraph, groups: [ list] } + - "/" + - { name: links, groups: [ links ] } + - { name: tools, groups: [ specialchar ] } + - { name: document, groups: [ mode ] } + + format_tags: "p;h1;h2;h3;h4" + + removePlugins: + - image + + removeButtons: + - Anchor + - Underline + - Strike + +buttons: + link: + queryParametersSelector: + enabled: 1 diff --git a/packages/ep_theme/Configuration/RequestMiddlewares.php b/packages/ep_theme/Configuration/RequestMiddlewares.php new file mode 100644 index 0000000..ec4ed8b --- /dev/null +++ b/packages/ep_theme/Configuration/RequestMiddlewares.php @@ -0,0 +1,12 @@ + [ + 'ep/referrer' => [ + 'target' => \EP\EpTheme\Middleware\ReferrerMiddleware::class, + 'after' => [ + 'typo3/cms-frontend/site', + ], + ], + ], +]; diff --git a/packages/ep_theme/Configuration/Services.yaml b/packages/ep_theme/Configuration/Services.yaml new file mode 100644 index 0000000..10d6f85 --- /dev/null +++ b/packages/ep_theme/Configuration/Services.yaml @@ -0,0 +1,9 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + EP\EpTheme\: + resource: '../Classes/*' + exclude: '../Classes/Domain/Model/*' diff --git a/packages/ep_theme/Configuration/TCA/Overrides/pages.php b/packages/ep_theme/Configuration/TCA/Overrides/pages.php new file mode 100644 index 0000000..dc6d891 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/pages.php @@ -0,0 +1,155 @@ + [ + 'exclude' => 0, + 'label' => 'PA Code für diese Seite', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'trim', + ], + ], + 'tx_eptheme_context_country_code' => [ + 'config' => [ + 'type' => 'passthrough' + ], + ], + 'tx_eptheme_context_country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'foreign_table_where' => 'ORDER BY name', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ] + ], + 'tx_eptheme_context_region' => [ + 'exclude' => 0, + 'label' => 'Gebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'foreign_table_where' => 'ORDER BY name', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ] + ], + 'tx_eptheme_hide_searchbar' => [ + 'exclude' => 0, + 'label' => 'Suchschlitz NICHT anzeigen', + 'config' => [ + 'type' => 'check' + ] + ], + 'tx_eptheme_new_tab' => [ + 'exclude' => 0, + 'label' => 'Seite in neuem Tab/Fenster öffnen', + 'config' => [ + 'type' => 'check' + ] + ], + 'tx_eptheme_bodyclass' => [ + 'exclude' => 0, + 'label' => 'CSS Klasse(n) für Body Tag', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'tx_eptheme_navitemclass' => [ + 'exclude' => 0, + 'label' => 'CSS Klasse(n) für Topnavigation', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'tx_eptheme_slides' => [ + 'exclude' => 0, + 'label' => 'Slides', + 'displayCond' => 'FIELD:backend_layout:!IN:pagets__detail,pagets__booking', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_eptheme_domain_model_slide', + 'foreign_field' => 'page', + 'foreign_sortby' => 'sorting', + 'maxitems' => 5, + 'appearance' => [ + 'useSortable' => 1, + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0 + ], + ], + ], + 'tx_eptheme_badge' => [ + 'exclude' => 0, + 'label' => 'Badge', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_badge', + 'foreign_table_where' => 'ORDER BY title', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + ]; + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns( + 'pages', + $temporaryColumns + ); + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( + 'pages', + '--div--;Kontext, + tx_eptheme_context_pacode,tx_eptheme_context_country,tx_eptheme_context_region, + tx_eptheme_hide_searchbar,tx_eptheme_new_tab,tx_eptheme_bodyclass,tx_eptheme_navitemclass, + --div--;Slider, + tx_eptheme_slides, + --div--;Badge, + tx_eptheme_badge', + '1,4' + ); + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerPageTSConfigFile( + 'ep_theme', + 'Configuration/TSconfig/Page/Main.tsconfig', + 'EP Theme' + ); + + $GLOBALS['TCA']['pages']['columns']['slug']['config']['generatorOptions']['fields'] = [ + [ + 'nav_title', + 'title' + ] + ]; + +}); + diff --git a/packages/ep_theme/Configuration/TCA/Overrides/sys_file.php b/packages/ep_theme/Configuration/TCA/Overrides/sys_file.php new file mode 100644 index 0000000..24bc1d9 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/sys_file.php @@ -0,0 +1,10 @@ + [ + 'default_sortby' => 'name DESC' + ], +]; + +\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TCA']['sys_file'], $tca); \ No newline at end of file diff --git a/packages/ep_theme/Configuration/TCA/Overrides/sys_template.php b/packages/ep_theme/Configuration/TCA/Overrides/sys_template.php new file mode 100644 index 0000000..495a32d --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/sys_template.php @@ -0,0 +1,13 @@ + [ + 'exclude' => 0, + 'label' => 'Button Label', + 'config' => [ + 'type' => 'input', + 'default' => 'Details', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'tx_eptheme_context_country' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_country', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'foreign_table_where' => 'ORDER BY name ASC', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'default' => 0, + 'minitems' => 0, + 'maxitems' => 1, + ] + ], + 'tx_eptheme_context_region' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_region', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'foreign_table_where' => 'ORDER BY name ASC', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'default' => 0, + 'minitems' => 0, + 'maxitems' => 1, + ] + ], + 'tx_eptheme_context_city' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_city', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_city', + 'foreign_table_where' => 'ORDER BY name ASC', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'default' => 0, + 'minitems' => 0, + 'maxitems' => 1, + ] + ], + 'tx_eptheme_context_hotel' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_hotel', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'foreign_table_where' => 'ORDER BY name ASC', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'default' => 0, + 'minitems' => 0, + 'maxitems' => 1, + ] + ], + 'tx_eptheme_context_concept' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_concept', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_concept', + 'foreign_table_where' => 'ORDER BY name ASC', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'default' => 0, + 'minitems' => 0, + 'maxitems' => 1, + ] + ], + 'tx_eptheme_context_product' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_product', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'foreign_table_where' => 'ORDER BY name_internal, name ASC', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'default' => 0, + 'minitems' => 0, + 'maxitems' => 1, + ] + ], + 'tx_eptheme_context_date_from' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang.xlf:tx_eptheme_context.date_from', + 'config' => [ + 'type' => 'input', + 'size' => 12, + 'eval' => 'date,int', + 'renderType' => 'inputDateTime', + ] + ], + 'tx_eptheme_context_date_to' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang.xlf:tx_eptheme_context.date_to', + 'config' => [ + 'type' => 'input', + 'size' => 12, + 'eval' => 'date,int', + 'renderType' => 'inputDateTime', + ] + ], + 'tx_eptheme_background' => [ + 'exclude' => 0, + 'label' => 'Hintergrundfarbe', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'default' => 0, + 'items' => [ + [ 'ohne', 0 ], + [ 'oben', 1 ], + [ 'unten', 2 ], + [ 'oben und unten', 3 ], + ], + ], + ], + ]; + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('tt_content', $ttContentColumns); + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette( + 'tt_content', + 'header', + '--linebreak--, + subheader;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:subheader_formlabel,--linebreak--', + 'after:header_layout' + ); + + $GLOBALS['TCA']['tt_content']['types']['textmedia']['columnsOverrides']['assets']['config']['overrideChildTca'] = [ + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ]; +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_button.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_button.php new file mode 100644 index 0000000..442fb58 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_button.php @@ -0,0 +1,44 @@ + ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --palette--;;tx_eptheme_button, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + 'columnsOverrides' => [ + 'header' => [ + 'label' => 'Buttonlabel', + ], + 'header_link' => [ + 'label' => 'URL/Seite', + ], + ], + ]; + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_calendar.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_calendar.php new file mode 100644 index 0000000..a4e84bd --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_calendar.php @@ -0,0 +1,30 @@ + ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + tx_eptheme_context_hotel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_contact.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_contact.php new file mode 100644 index 0000000..90b5ebe --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_contact.php @@ -0,0 +1,44 @@ + 0, + 'label' => 'Mitarbeiter', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_teammember', + 'foreign_table_where' => 'ORDER BY name', + 'minitems' => 1, + 'maxitems' => 1, + ] + ]; + + $GLOBALS['TCA']['tt_content']['types']['contact'] = [ + 'showitem' => ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + tx_eptheme_contact, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_container.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_container.php new file mode 100644 index 0000000..14d1ddb --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_container.php @@ -0,0 +1,52 @@ +configureContainer( + new \B13\Container\Tca\ContainerConfiguration( + 'ep_container_quarters', + 'Vierspalter', + '25/25/25/25 Aufteilung mit groß/klein Umschaltung', + [ + [ + ['name' => 'Elemente', 'colPos' => 300], + ] + ] + ) + ); + + \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\B13\Container\Tca\Registry::class)->configureContainer( + new \B13\Container\Tca\ContainerConfiguration( + 'ep_container_teasergroup', + 'Dreispalter', + '33/33/33 Aufteilung mit groß/klein Umschaltung', + [ + [ + ['name' => 'Teaser', 'colPos' => 300], + ] + ] + ) + ); + + $headerConfig = [ + 'showitem' => ' + --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:palette.general, + --palette--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:palette.general;general, + --palette--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:palette.header;header, + --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, + --palette--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:palette.frames;frames, + --palette--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:palette.appearanceLinks;appearanceLinks, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, + --palette--;;language, + --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.access, + --palette--;;hidden, + --palette--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:palette.access;access + ' + ]; + + foreach (['ep_container_quarters', 'ep_container_teasergroup'] as $field) { + $GLOBALS['TCA']['tt_content']['types'][$field] = array_replace_recursive( + $GLOBALS['TCA']['tt_content']['types'][$field], + $headerConfig + ); + } +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_deal_of_the_week.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_deal_of_the_week.php new file mode 100644 index 0000000..c278c7f --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_deal_of_the_week.php @@ -0,0 +1,39 @@ + ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, header, header_link, bodytext, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, tx_eptheme_background, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + 'columnsOverrides' => [ + 'bodytext' => [ + 'label' => 'Text', + 'config' => [ + 'rows' => 2, + ] + ], + ] + ]; + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_disrupter.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_disrupter.php new file mode 100644 index 0000000..7edde28 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_disrupter.php @@ -0,0 +1,54 @@ + 0, + 'label' => 'Störer', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_eptheme_domain_model_disrupter', + 'foreign_field' => 'tt_content', + 'maxitems' => 1, + 'minitems' => 1, + 'foreign_table_where' => 'ORDER BY headline ASC', + 'MM' => 'tx_eptheme_ttcontent_tx_eptheme_disrupter_mm', + ], + ]; + + $GLOBALS['TCA']['tt_content']['types']['disrupter'] = [ + 'showitem' => ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, tx_eptheme_disrupter, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, tx_eptheme_background, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + 'columnsOverrides' => [ + ] + ]; + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPiFlexFormValue( + '*', + 'FILE:EXT:ep_theme/Configuration/FlexForms/flexform_disturber.xml', + 'disrupter' + ); + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_disturber.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_disturber.php new file mode 100644 index 0000000..73824e0 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_disturber.php @@ -0,0 +1,59 @@ + ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --palette--;' . $frontendLanguageFilePrefix . 'palette.header;header, bodytext, pi_flexform, + --div--;Bild, + assets, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, tx_eptheme_background, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + 'columnsOverrides' => [ + 'bodytext' => [ + 'label' => 'Text', + 'config' => [ + 'enableRichtext' => true, + ] + ], + 'assets' => [ + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig('assets', [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'Hinzufügen' + ], + 'foreign_types' => $GLOBALS['TCA']['tt_content']['columns']['image']['config']['foreign_types'], + 'maxitems' => 1, + 'minitems' => 0, + ], $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']) + ] + ] + ]; + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPiFlexFormValue( + '*', + 'FILE:EXT:ep_theme/Configuration/FlexForms/flexform_disturber.xml', + 'disturber' + ); + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_event_pricetable.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_event_pricetable.php new file mode 100644 index 0000000..4a23161 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_event_pricetable.php @@ -0,0 +1,30 @@ + ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + tx_eptheme_context_product, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_faq_overview.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_faq_overview.php new file mode 100644 index 0000000..ff217a3 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_faq_overview.php @@ -0,0 +1,28 @@ + ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_gmap.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_gmap.php new file mode 100644 index 0000000..d4466b1 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_gmap.php @@ -0,0 +1,30 @@ + ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + tx_eptheme_context_region, tx_eptheme_context_hotel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_lineup.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_lineup.php new file mode 100644 index 0000000..4c38bf5 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_lineup.php @@ -0,0 +1,50 @@ + 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_db.xlf:tx_eptheme_domain_model_lineup.items', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_eptheme_domain_model_lineup', + 'foreign_field' => 'tt_content', + 'foreign_sortby' => 'sorting', + 'maxitems' => 99, + 'appearance' => [ + 'useSortable' => 1, + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0 + ], + ], + ]; + + $GLOBALS['TCA']['tt_content']['types']['lineup'] = [ + 'showitem' => ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + tx_eptheme_lineup, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_nlform.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_nlform.php new file mode 100644 index 0000000..5753acd --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_nlform.php @@ -0,0 +1,33 @@ + ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general', + 'columnsOverrides' => [ + 'header_link' => [ + 'label' => 'YouTube URL/Vimeo ID', + ], + 'layout' => [ + 'label' => 'Typ', + ], + ], + ]; + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_teaser.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_teaser.php new file mode 100644 index 0000000..1fec92c --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_teaser.php @@ -0,0 +1,256 @@ + ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --palette--;' . $frontendLanguageFilePrefix . 'palette.header;header,bodytext,button, + --div--;Bild, + assets, + --div--;Kontext, + tx_eptheme_context_country,tx_eptheme_context_region, tx_eptheme_context_hotel, tx_eptheme_context_concept, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + 'columnsOverrides' => [ + 'bodytext' => [ + 'label' => 'Teasertext', + 'config' => [ + 'enableRichtext' => true, + ], + ], + 'header' => [ + 'config' => [ + 'type' => 'text', + 'rows' => 2, + ] + ], + 'assets' => [ + 'label' => 'Teaserbild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig('assets', [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'Hinzufügen' + ], + 'overrideChildTca' => [ + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaserEven' => [ + 'title' => 'Teaser gerade', + 'value' => 1.3333333333333334, + ], + 'teaserOdd' => [ + 'title' => 'Teaser ungerade', + 'value' => 2.0555555555555556, + ], + 'free' => [ + 'title' => 'Frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 0, + ], $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']) + ] + ] + ]; + + $GLOBALS['TCA']['tt_content']['types']['teaser_country'] = [ + 'showitem' => ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --palette--;Kontext, tx_eptheme_context_country, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + + $GLOBALS['TCA']['tt_content']['types']['teaser_region'] = [ + 'showitem' => ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --palette--;Kontext, tx_eptheme_context_region, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + + $GLOBALS['TCA']['tt_content']['types']['teaser_city'] = [ + 'showitem' => ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --palette--;Kontext, tx_eptheme_context_city, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + + $GLOBALS['TCA']['tt_content']['types']['teaser_hotel'] = [ + 'showitem' => ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --palette--;Kontext, tx_eptheme_context_hotel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + + $GLOBALS['TCA']['tt_content']['types']['teaser_concept'] = [ + 'showitem' => ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --palette--;Kontext, tx_eptheme_context_concept, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + + $GLOBALS['TCA']['tt_content']['types']['teaser_product'] = [ + 'showitem' => ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --palette--;Kontext, tx_eptheme_context_product, tx_eptheme_context_hotel, + tx_eptheme_context_date_from, tx_eptheme_context_date_to, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + + $GLOBALS['TCA']['tt_content']['types']['teaser_booking_link'] = [ + 'showitem' => ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + --palette--;Kontext, tx_eptheme_context_product, + --div--;' . $frontendLanguageFilePrefix . 'tabs.appearance, + layout;' . $frontendLanguageFilePrefix . 'layout_formlabel, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + ]; + +}); diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_ytvideo.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_ytvideo.php new file mode 100644 index 0000000..6d9230e --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_ytvideo.php @@ -0,0 +1,38 @@ + ' + --palette--;' . $frontendLanguageFilePrefix . 'palette.general;general, + layout, header_link, + --div--;' . $frontendLanguageFilePrefix . 'tabs.access, + hidden;' . $frontendLanguageFilePrefix . 'field.default.hidden, + --div--;' . $frontendLanguageFilePrefix . 'tabs.extended + ', + 'columnsOverrides' => [ + 'header_link' => [ + 'label' => 'YouTube URL/Vimeo ID', + ], + 'layout' => [ + 'label' => 'Typ', + ], + ], + ]; + +}); diff --git a/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_addresscheck.php b/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_addresscheck.php new file mode 100644 index 0000000..304d471 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_addresscheck.php @@ -0,0 +1,169 @@ + [ + 'label' => 'name', + 'label_alt' => 'email', + 'default_sortby' => 'ORDER BY last_name, first_name', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'prependAtCopy' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.prependAtCopy', + 'delete' => 'deleted', + 'title' => 'Adresscheck', + 'versioningWS' => true, + 'origUid' => 't3_origuid', + 'enablecolumns' => [ + 'disabled' => 'hidden' + ], + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + 'searchFields' => 'name, first_name, last_name, email', + 'dividers2tabs' => 1, + ], + 'columns' => [ + 'pid' => [ + 'label' => 'pid', + 'config' => [ + 'type' => 'passthrough' + ] + ], + 'crdate' => [ + 'label' => 'crdate', + 'config' => [ + 'type' => 'passthrough', + ] + ], + 'cruser_id' => [ + 'label' => 'cruser_id', + 'config' => [ + 'type' => 'passthrough' + ] + ], + 'tstamp' => [ + 'label' => 'tstamp', + 'config' => [ + 'type' => 'passthrough', + ] + ], + 'hidden' => [ + 'exclude' => 1, + 'label' => 'Versteckt', + 'config' => [ + 'type' => 'check' + ] + ], + 'customer_id' => [ + 'exclude' => 1, + 'label' => 'Kundennummer', + 'config' => [ + 'type' => 'input', + 'size' => '8', + 'eval' => 'trim', + 'max' => '255' + ] + ], + 'title' => [ + 'exclude' => 1, + 'label' => 'Anrede', + 'config' => [ + 'type' => 'input', + 'size' => '8', + 'eval' => 'trim', + 'max' => '255' + ] + ], + 'first_name' => [ + 'exclude' => 0, + 'label' => 'Vorname', + 'config' => [ + 'type' => 'input', + 'size' => '20', + 'eval' => 'trim', + 'max' => '255' + ] + ], + 'last_name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => '20', + 'eval' => 'trim', + 'max' => '255' + ] + ], + 'address' => [ + 'label' => 'Strasse / Nr.', + 'config' => [ + 'type' => 'text', + 'cols' => '20', + 'rows' => '3' + ] + ], + 'email' => [ + 'label' => 'E-Mail', + 'config' => [ + 'type' => 'input', + 'size' => '20', + 'eval' => 'trim', + 'max' => '255', + 'softref' => 'email' + ] + ], + 'city' => [ + 'label' => 'Stadt', + 'config' => [ + 'type' => 'input', + 'size' => '20', + 'eval' => 'trim', + 'max' => '255' + ] + ], + 'zip' => [ + 'label' => 'PLZ', + 'config' => [ + 'type' => 'input', + 'eval' => 'trim', + 'size' => '10', + 'max' => '20' + ] + ], + 'country' => [ + 'exclude' => 1, + 'label' => 'Land', + 'config' => [ + 'type' => 'input', + 'size' => '20', + 'eval' => 'trim', + 'max' => '128' + ] + ], + 'no_postal' => [ + 'exclude' => 1, + 'label' => 'Keine Postalische Werbung', + 'config' => [ + 'type' => 'check' + ] + ], + 'no_email' => [ + 'exclude' => 1, + 'label' => 'Keine E-Mails mehr', + 'config' => [ + 'type' => 'check' + ] + ], + 'address_current' => [ + 'exclude' => 1, + 'label' => 'Adresse ist aktuell', + 'config' => [ + 'type' => 'check' + ] + ], + ], + 'types' => [ + '0' => ['showitem' => + 'hidden, + title,first_name,last_name,address,zip,city,country,email,no_postal,no_email,address_current, + '] + ], +]; diff --git a/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_disrupter.php b/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_disrupter.php new file mode 100644 index 0000000..2caaca6 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_disrupter.php @@ -0,0 +1,211 @@ + [ + 'title' => 'Störer', + 'default_sortby' => 'ORDER BY headline', + 'label' => 'headline', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'headline,text', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'sys_language_uid, hidden, layout, headline, text, image, pi_flexform', + ], + ], + 'palettes' => [ + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_eptheme_domain_model_disrupter', + 'foreign_table_where' => 'AND tx_eptheme_domain_model_disrupter.pid=###CURRENT_PID### AND tx_eptheme_domain_model_disrupter.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'layout' => [ + 'exclude' => 0, + 'label' => 'Layout', + 'config' => [ + 'type' => 'selectSingle', + 'items' => [ + ['klein', 0], + ['groß', 1], + ['Event klein', 2], + ['Event groß', 3], + ], + ], + ], + 'text' => [ + 'exclude' => 0, + 'label' => 'Text', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 5, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'image' => [ + 'exclude' => 0, + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference', + 'fileUploadAllowed' => false, + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette' + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'square' => [ + 'title' => 'quadratisch', + 'value' => 1, + ], + 'free' => [ + 'title' => 'Frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + 'jpg,jpeg' + ), + ], + 'pi_flexform' => [ + 'exclude' => 0, + 'label' => 'Buttons', + 'config' => [ + 'type' => 'flex', + 'ds' => [ + 'default' => 'FILE:EXT:ep_theme/Configuration/FlexForms/flexform_disturber.xml', + ], + ] + ], + 'tt_content' => [ + 'exclude' => 0, + 'config' => [ + 'type' => 'passthrough', + ], + ], + ], +]; diff --git a/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_lineup.php b/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_lineup.php new file mode 100644 index 0000000..7beb66d --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_lineup.php @@ -0,0 +1,224 @@ + [ + 'title' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_db.xlf:tx_eptheme_domain_model_lineup', + 'default_sortby' => 'ORDER BY name', + 'label' => 'name', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'headline,subline', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, name, description, image, --palette--;;social', + ], + ], + 'palettes' => [ + 'social' => ['showitem' => 'facebook_url, twitter_url, youtube_url, instagram_url, soundcloud_url'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_eptheme_domain_model_lineup', + 'foreign_table_where' => 'AND tx_eptheme_domain_model_lineup.pid=###CURRENT_PID### AND tx_eptheme_domain_model_lineup.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ], + ], + + 'name' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_db.xlf:tx_eptheme_domain_model_lineup.name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang.xlf:tx_eptheme_domain_model_lineup.description', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 10, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'image' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_db.xlf:tx_eptheme_domain_model_lineup.image', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference', + 'useSortable' => false, + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'facebook_url' => [ + 'exclude' => 0, + 'label' => 'Facebook URL', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'twitter_url' => [ + 'exclude' => 0, + 'label' => 'Twitter URL', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'instagram_url' => [ + 'exclude' => 0, + 'label' => 'Instagram URL', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'youtube_url' => [ + 'exclude' => 0, + 'label' => 'YouTube URL', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'soundcloud_url' => [ + 'exclude' => 0, + 'label' => 'Soundcloud URL', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'tt_content' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_db.xlf:tx_eptheme_domain_model_lineup.tt_content', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tt_content', + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + ], +]; diff --git a/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_slide.php b/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_slide.php new file mode 100644 index 0000000..8f9e942 --- /dev/null +++ b/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_slide.php @@ -0,0 +1,255 @@ + [ + 'title' => 'Slide', + 'default_sortby' => 'ORDER BY headline', + 'label' => 'headline', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'headline,subline', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, --palette--;;headlines, --palette--;;linkbutton, image', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden, skipass'], + 'headlines' => ['showitem' => 'headline, subline'], + 'linkbutton' => ['showitem' => 'button, page_uid'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_eptheme_domain_model_slide', + 'foreign_table_where' => 'AND tx_eptheme_domain_model_slide.pid=###CURRENT_PID### AND tx_eptheme_domain_model_slide.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ], + ], + + 'headline' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_db.xlf:tx_eptheme_domain_model_slide.headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'subline' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_db.xlf:tx_eptheme_domain_model_slide.subline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'button' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_db.xlf:tx_eptheme_domain_model_slide.button', + 'config' => [ + 'type' => 'input', + 'size' => 20, + 'eval' => 'trim' + ], + ], + 'skipass' => [ + 'exclude' => 0, + 'label' => 'Skipass Badge', + 'config' => [ + 'type' => 'check', + ], + ], + 'page_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_db.xlf:tx_eptheme_domain_model_slide.page_uid', + 'config' => [ + 'type' => 'input', + 'size' => 20, + 'renderType' => 'inputLink', + ] + ], + 'image' => [ + 'exclude' => 0, + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference', + 'fileUploadAllowed' => false, + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/Resources/Private/Language/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette' + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'contentSlider' => [ + 'title' => 'Content Slider/Galerie', + 'value' => 1.59, + ], + 'contentSliderProduct' => [ + 'title' => 'Content Slider/Galerie Produkttexte', + 'value' => 1.79069767, + ], + 'sliderProductHeader' => [ + 'title' => 'Slider Produkt-Header', + 'value' => 1.625, + ], + 'sliderPageHeader' => [ + 'title' => 'Slider Seiten-Header', + 'value' => 3.805, + ], + 'sliderPageHeaderHome' => [ + 'title' => 'Slider Seiten-Header Startseite', + 'value' => 1370 / 585, + ], + 'free' => [ + 'title' => 'Frei', + 'value' => 'NaN', + ], + ], + ], + 'mobile' => [ + 'title' => 'Mobile', + 'allowedAspectRatios' => [ + 'sliderPageHeader' => [ + 'title' => 'Slider Seiten-Header', + 'value' => 1, + ], + 'free' => [ + 'title' => 'Frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + 'jpg,jpeg' + ), + ], + 'page' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_db.xlf:tx_eptheme_domain_model_slide.page', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'pages', + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + ], +]; diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Extensions/GridElements.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Extensions/GridElements.tsconfig new file mode 100644 index 0000000..f2b6a0c --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Extensions/GridElements.tsconfig @@ -0,0 +1,180 @@ +tx_gridelements.setup { + quarters { + title = Teaserreihe vierspaltig (25%/25%/25%/25%) + topLevelLayout = 1 + icon = EXT:ep_theme/Resources/Public/images/tx_eptheme_gridelements_quarters.png + config { + colCount = 4 + rowCount = 1 + rows { + 1 { + columns { + 1 { + name = Spalte 1 + colPos = 41 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + 2 { + name = Spalte 2 + colPos = 42 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + 3 { + name = Spalte 3 + colPos = 43 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + 4 { + name = Spalte 4 + colPos = 44 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + } + } + } + } + } + thirds { + title = Teaserreihe dreispaltig (33%/33%/33%) + topLevelLayout = 1 + icon = EXT:ep_theme/Resources/Public/images/tx_eptheme_gridelements_thirds.png + config { + colCount = 3 + rowCount = 1 + rows { + 1 { + columns { + 1 { + name = Spalte 1 + colPos = 31 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + 2 { + name = Spalte 2 + colPos = 32 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + 3 { + name = Spalte 3 + colPos = 33 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + } + } + } + } + } + halves { + title = Teaserreihe zweispaltig (50%/50%) + topLevelLayout = 1 + icon = EXT:ep_theme/Resources/Public/images/tx_eptheme_gridelements_halves.png + config { + colCount = 2 + rowCount = 1 + rows { + 1 { + columns { + 1 { + name = Spalte 1 + colPos = 21 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + 2 { + name = Spalte 2 + colPos = 22 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + } + } + } + } + } + halfAndTwoQuarters { + title = Teaserreihe dreispaltig (50%/25%/25%) + topLevelLayout = 1 + icon = EXT:ep_theme/Resources/Public/images/tx_eptheme_gridelements_half_and_two_quarters.png + config { + colCount = 4 + rowCount = 1 + rows { + 1 { + columns { + 1 { + name = Spalte 1 + colPos = 51 + colspan = 2 + allowed = textmedia,teaser,list,form_formframework + } + 2 { + name = Spalte 2 + colPos = 52 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + 3 { + name = Spalte 3 + colPos = 53 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + } + } + } + } + } + twoQuartersAndHalf { + title = Teaserreihe dreispaltig (25%/25%/50%) + topLevelLayout = 1 + icon = EXT:ep_theme/Resources/Public/images/tx_eptheme_gridelements_two_quarters_and_half.png + config { + colCount = 4 + rowCount = 1 + rows { + 1 { + columns { + 1 { + name = Spalte 1 + colPos = 61 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + 2 { + name = Spalte 2 + colPos = 62 + disallowed { + CType = menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages + } + } + 3 { + name = Spalte 3 + colPos = 63 + colspan = 2 + allowed = textmedia,teaser,list,gmap,nlform,form_formframework,menu_pages,menu_subpages,menu_section + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Main.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Main.tsconfig new file mode 100644 index 0000000..e8fe637 --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Main.tsconfig @@ -0,0 +1,7 @@ +@import 'EXT:ep_theme/Configuration/TSconfig/Page/Extensions/GridElements.tsconfig' +@import 'EXT:ep_theme/Configuration/TSconfig/Page/Mod/SHARED.tsconfig' +@import 'EXT:ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts.tsconfig' +@import 'EXT:ep_theme/Configuration/TSconfig/Page/Mod/Wizards/NewContentElement.tsconfig' +@import 'EXT:ep_theme/Configuration/TSconfig/Page/RTE.tsconfig' +@import 'EXT:ep_theme/Configuration/TSconfig/Page/TCEFORM.tsconfig' +@import 'EXT:ep_theme/Configuration/TSconfig/Page/TCEMAIN.tsconfig' diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/SHARED.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/SHARED.tsconfig new file mode 100644 index 0000000..cc66c5c --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/SHARED.tsconfig @@ -0,0 +1,6 @@ +mod { + SHARED { + defaultLanguageFlag = de + defaultLanguageLabel = Deutsch + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts.tsconfig new file mode 100644 index 0000000..4675d34 --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts.tsconfig @@ -0,0 +1 @@ +@import 'EXT:ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/*' diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Detail.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Detail.tsconfig new file mode 100644 index 0000000..65c0807 --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Detail.tsconfig @@ -0,0 +1,29 @@ +mod.web_layout.BackendLayouts { + detail { + title = Detailseite + config { + backend_layout { + colCount = 1 + rowCount = 2 + rows { + 1 { + columns { + 1 { + name = Content + colPos = 0 + } + } + } + 2 { + columns { + 1 { + name = Teaserbereich + colPos = 1 + } + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Frontpage.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Frontpage.tsconfig new file mode 100644 index 0000000..43dea40 --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Frontpage.tsconfig @@ -0,0 +1,21 @@ +mod.web_layout.BackendLayouts { + frontpage { + title = Startseite + config { + backend_layout { + colCount = 1 + rowCount = 1 + rows { + 1 { + columns { + 1 { + name = Content + colPos = 0 + } + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/FrontpageEvent.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/FrontpageEvent.tsconfig new file mode 100644 index 0000000..0857aa8 --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/FrontpageEvent.tsconfig @@ -0,0 +1,22 @@ +mod.web_layout.BackendLayouts { + frontpageEvent { + title = Startseite Events + config { + backend_layout { + colCount = 1 + rowCount = 1 + rows { + 1 { + columns { + 1 { + name = Content + colPos = 0 + } + } + } + } + } + } + } +} + diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Popup.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Popup.tsconfig new file mode 100644 index 0000000..32d7efe --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Popup.tsconfig @@ -0,0 +1,21 @@ +mod.web_layout.BackendLayouts { + popup { + title = Popup + config { + backend_layout { + colCount = 1 + rowCount = 1 + rows { + 1 { + columns { + 1 { + name = Content + colPos = 0 + } + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Search.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Search.tsconfig new file mode 100644 index 0000000..3fdc8ae --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Search.tsconfig @@ -0,0 +1,29 @@ +mod.web_layout.BackendLayouts { + search { + title = Suchergebnis + config { + backend_layout { + colCount = 1 + rowCount = 2 + rows { + 1 { + columns { + 1 { + name = Content + colPos = 0 + } + } + } + 2 { + columns { + 1 { + name = Teaserbereich + colPos = 1 + } + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Subpage.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Subpage.tsconfig new file mode 100644 index 0000000..f24920e --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Subpage.tsconfig @@ -0,0 +1,29 @@ +mod.web_layout.BackendLayouts { + subpage { + title = Unterseite + config { + backend_layout { + colCount = 1 + rowCount = 2 + rows { + 1 { + columns { + 1 { + name = Content + colPos = 0 + } + } + } + 2 { + columns { + 1 { + name = Teaserbereich + colPos = 1 + } + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarLeft.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarLeft.tsconfig new file mode 100644 index 0000000..968f3ae --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarLeft.tsconfig @@ -0,0 +1,47 @@ +mod.web_layout.BackendLayouts { + subpageSidebarLeft { + title = Unterseite mit Sidebar (links) + config { + backend_layout { + colCount = 4 + rowCount = 3 + rows { + 1 { + columns { + 1 { + name = Headline + colPos = 3 + colspan = 4 + } + } + } + 2 { + columns { + 1 { + name = Sidebar + colPos = 2 + colspan = 1 + } + } + columns { + 2 { + name = Content + colPos = 4 + colspan = 3 + } + } + } + 3 { + columns { + 1 { + name = Teaserbereich + colPos = 1 + colspan = 4 + } + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarLeftEvent.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarLeftEvent.tsconfig new file mode 100644 index 0000000..4655441 --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarLeftEvent.tsconfig @@ -0,0 +1,47 @@ +mod.web_layout.BackendLayouts { + subpageSidebarLeftEvent { + title = Unterseite Events mit Sidebar (links) + config { + backend_layout { + colCount = 4 + rowCount = 3 + rows { + 1 { + columns { + 1 { + name = Headline + colPos = 3 + colspan = 4 + } + } + } + 2 { + columns { + 1 { + name = Sidebar + colPos = 2 + colspan = 1 + } + } + columns { + 2 { + name = Content + colPos = 4 + colspan = 3 + } + } + } + 3 { + columns { + 1 { + name = Teaserbereich + colPos = 1 + colspan = 4 + } + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarRight.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarRight.tsconfig new file mode 100644 index 0000000..d629091 --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarRight.tsconfig @@ -0,0 +1,47 @@ +mod.web_layout.BackendLayouts { + subpageSidebarRight { + title = Unterseite mit Sidebar (rechts) + config { + backend_layout { + colCount = 4 + rowCount = 3 + rows { + 1 { + columns { + 1 { + name = Headline + colPos = 3 + colspan = 4 + } + } + } + 2 { + columns { + 1 { + name = Content + colPos = 4 + colspan = 3 + } + } + columns { + 2 { + name = Sidebar + colPos = 2 + colspan = 1 + } + } + } + 3 { + columns { + 1 { + name = Teaserbereich + colPos = 1 + colspan = 4 + } + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarRightEvent.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarRightEvent.tsconfig new file mode 100644 index 0000000..152e94a --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarRightEvent.tsconfig @@ -0,0 +1,47 @@ +mod.web_layout.BackendLayouts { + subpageSidebarRightEvent { + title = Unterseite Events mit Sidebar (rechts) + config { + backend_layout { + colCount = 4 + rowCount = 3 + rows { + 1 { + columns { + 1 { + name = Headline + colPos = 3 + colspan = 4 + } + } + } + 2 { + columns { + 1 { + name = Content + colPos = 4 + colspan = 3 + } + } + columns { + 2 { + name = Sidebar + colPos = 2 + colspan = 1 + } + } + } + 3 { + columns { + 1 { + name = Teaserbereich + colPos = 1 + colspan = 4 + } + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Travelinfo.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Travelinfo.tsconfig new file mode 100644 index 0000000..9cdca4d --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Travelinfo.tsconfig @@ -0,0 +1,21 @@ +mod.web_layout.BackendLayouts { + travelinfo { + title = Reiseinformationen + config { + backend_layout { + colCount = 1 + rowCount = 1 + rows { + 1 { + columns { + 1 { + name = Content + colPos = 0 + } + } + } + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/Mod/Wizards/NewContentElement.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/Mod/Wizards/NewContentElement.tsconfig new file mode 100644 index 0000000..8f3aed3 --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/Mod/Wizards/NewContentElement.tsconfig @@ -0,0 +1,151 @@ +mod { + wizards.newContentElement.wizardItems { + common.show := removeFromList(table,bullets,uploads) + menu.show := removeFromList(menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages) + special.show = div,shortcut,html + forms.show = formframework + teaser { + header = Teaser + elements { + teaser { + iconIdentifier = content-ep_theme-ce + title = Teaser allgemein + description = Teaser mit konfigurierbarem Kontext + tt_content_defValues.CType = teaser + } + teaser_country { + iconIdentifier = content-ep_theme-ce + title = Teaser Land + description = Teaser für ein auswählbares Land + tt_content_defValues.CType = teaser_country + } + teaser_region { + iconIdentifier = content-ep_theme-ce + title = Teaser Gebiet + description = Teaser für ein auswählbares Gebiet + tt_content_defValues.CType = teaser_region + } + teaser_city { + iconIdentifier = content-ep_theme-ce + title = Teaser Ort + description = Teaser für einen auswählbaren Ort + tt_content_defValues.CType = teaser_city + } + teaser_hotel { + iconIdentifier = content-ep_theme-ce + title = Teaser Hotel + description = Teaser für ein auswählbares Hotel + tt_content_defValues.CType = teaser_hotel + } + teaser_concept { + iconIdentifier = content-ep_theme-ce + title = Teaser Konzept + description = Teaser für ein auswählbares Konzept + tt_content_defValues.CType = teaser_concept + } + teaser_product { + iconIdentifier = content-ep_theme-ce + title = Teaser Produkt + description = Teaser für ein auswählbares Produkt + tt_content_defValues.CType = teaser_product + } + teaser_booking_link { + iconIdentifier = content-ep_theme-ce + title = Teaser Buchungslink + description = Teaser für ein auswählbares Produkt (Buchungslink) + tt_content_defValues.CType = teaser_booking_link + } + } + show := addToList(teaser, teaser_country, teaser_region, teaser_city, teaser_hotel, teaser_concept) + show := addToList(teaser_product, teaser_booking_link) + } + disturber { + header = Störer + elements { + disturber { + iconIdentifier = content-ep_theme-ce + title = Störer + description = Störer mit Bild, mehreren Buttons und Kontaktinfo + tt_content_defValues.CType = disturber + } + disrupter { + iconIdentifier = content-ep_theme-ce + title = Störer (neu) + description = Störer mit Bild, mehreren Buttons und Kontaktinfo + tt_content_defValues.CType = disrupter + } + deal_of_the_week { + iconIdentifier = content-ep_theme-ce + title = Deal der Woche + description = Störer 'Deal der Woche' mit link auf Produkt + tt_content_defValues.CType = deal_of_the_week + } + } + show := addToList(disturber, disrupter, deal_of_the_week) + } + special { + elements { + faq_overview { + iconIdentifier = content-ep_theme-ce + title = FAQ Alle Kategorien + description = Alle FAQ-Einträge nach Kategorie gruppiert + tt_content_defValues.CType = faq_overview + } + lineup { + iconIdentifier = content-ep_theme-ce + title = Lineup + description = Links/rechts alternierende Boxen + tt_content_defValues.CType = lineup + } + event_pricetable { + iconIdentifier = content-ep_theme-ce + title = Event Preistabelle + description = Preistabelle nach Verfügbarkeit + tt_content_defValues.CType = event_pricetable + } + nlform { + iconIdentifier = content-ep_theme-ce + title = Newsletter Anmeldung + description = Anmeldeformular + tt_content_defValues.CType = nlform + } + } + show := addToList(faq_overview, lineup, event_pricetable, nlform) + } + common { + elements { + gmap { + iconIdentifier = content-ep_theme-ce + title = OSM Map + description = OSM Map für Hotel oder Gebiet + tt_content_defValues.CType = gmap + } + contact { + iconIdentifier = content-ep_theme-ce + title = Ansprechpartner + description = Kontaktbox mit Ansprechpartner für Sidebar + tt_content_defValues.CType = contact + } + calendar { + iconIdentifier = content-ep_theme-ce + title = Belegungskalender + description = Belegungskalender zu einem ausgewählten Vertragspartner + tt_content_defValues.CType = calendar + } + ytvideo { + iconIdentifier = content-ep_theme-ce + title = YouTube Video + description = Responsiv eingebettetes Video + tt_content_defValues.CType = ytvideo + } + button { + iconIdentifier = content-ep_theme-ce + title = Button + description = Button mit Referrer Übertragung + tt_content_defValues.CType = button + } + } + show := addToList(gmap, contact, calendar, ytvideo, button) + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/RTE.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/RTE.tsconfig new file mode 100644 index 0000000..5af5eec --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/RTE.tsconfig @@ -0,0 +1,21 @@ +RTE { + config { + tt_content { + bodytext { + types { + text { + preset = ep-reisen + } + textmedia { + preset = ep-reisen + } + } + } + } + tx_epproducts_domain_model_travelinfo { + intro_text { + preset = ep-minimal + } + } + } +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/TCEFORM.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/TCEFORM.tsconfig new file mode 100644 index 0000000..6256d8d --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/TCEFORM.tsconfig @@ -0,0 +1,179 @@ +TCEFORM { + tt_content { + CType { + # Remove content elements added by EXT:ep_events + removeItems := addToList(tile, statement, herounit, banner) + } + + imageorient.removeItems := addToList(0,1,2,8,9,25,26) + imageorient.disabled = 1 + imagecols.disabled = 1 + image_noRows.disabled = 1 + imageborder.disabled = 1 + imagewidth.disabled = 1 + imageheight.disabled = 1 + image_link.disabled = 1 + image_zoom = 1 + image_compression.disabled = 1 + image_effects.disabled = 1 + image_frames.disabled = 1 + imagecaption_position.disabled = 1 + + header_layout { + altLabels { + 1 = H1 + 2 = H2 + 3 = H3 + 4 = H4 + 5 = H5 + } + addItems { + 6 = H1 (mit Unterstreichung) + 7 = H2 (mit Unterstreichung) + 8 = H1 (mit Topline) + 9 = H2 (mit Topline) + } + } + + layout { + disabled = 1 + types { + textmedia { + disabled = 0 + removeItems = 1 + altLabels { + 2 = Slider + 3 = Link Panel + } + addItems { + 4 = Slider (ohne Thumbnails) + 5 = Partnerlogos + } + } + gridelements_pi1 { + disabled = 0 + removeItems := addToList(1,2,3) + } + teaser { + disabled = 0 + removeItems := addToList(2,3) + altLabels { + 0 = einspaltig + 1 = zweispaltig + } + } + disturber { + disabled = 0 + altLabels { + 0 = klein + 1 = groß + 2 = Event klein + 3 = Event groß + } + } + teaser_product { + disabled = 0 + removeItems := addToList(0,2) + altLabels { + 1 = einspaltig + 3 = mehrspaltig/quer + } + } + teaser_booking_link { + disabled = 0 + removeItems := addToList(0,2) + altLabels { + 1 = einspaltig + 3 = mehrspaltig/quer + } + } + ytvideo { + disabled = 0 + removeItems := addToList(2,3) + altLabels { + 0 = YouTube + 1 = Vimeo + } + } + ep_container_teasergroup { + disabled = 0 + removeItems := addToList(2,3) + altLabels { + 0 = ohne Hintergrund + 1 = mit Hintergrund + } + } + ep_container_quarters < .ep_container_teasergroup + } + } + + colPos { + label = Slot + altLabels.1848 = Blogeintrag + } + + space_before_class { + types { + gridelements_pi1.disabled = 1 + form_formframework.disabled = 1 + } + removeItems := addToList(extra-small, medium, extra-large) + altLabels.. = Standard (ohne) + } + + space_after_class < .space_before_class + + frame_class.disabled = 1 + table_bgColor.disabled = 1 + table_border.disabled = 1 + table_cellspacing.disabled = 1 + table_cellpadding.disabled = 1 + filelink_size.disabled = 1 + uploads_description.disabled = 1 + uploads_type.disabled = 1 + rowDescription.disabled = 1 + categories.disabled = 1 + date.disabled = 1 + select_key.disabled = 1 + linkToTop.disabled = 1 + header_position.disabled = 1 + header.label = Headline + subheader.label = Topline + fe_group.disabled = 1 + editlock.disabled = 1 + sys_language_uid.disabled = 1 + tx_epevents_configurator_travel_type.disabled = 1 + tx_yoastseo_linking_suggestions.disabled = 1 + } + + pages { + cache_tags.disabled = 1 + fe_login_mode.disabled = 1 + layout.disabled = 1 + abstract.disabled = 1 + author.disabled = 1 + author_email.disabled = 1 + lastUpdated.disabled = 1 + newUntil.disabled = 1 + alias.disabled = 1 + notes.disabled = 1 + categories.disabled = 1 + sys_language_uid.disabled = 1 + l18n_cfg.disabled = 1 + module.disabled = 0 + media.disabled = 1 + } + + tx_news_domain_model_news { + notes.disabled = 1 + keywords.disabled = 1 + editlock.disabled = 1 + fe_group.disabled = 1 + type.disabled = 1 + sys_language_uid.disabled = 1 + author_email.disabled = 1 + starttime.disabled = 1 + endtime.disabled = 1 + } + +} diff --git a/packages/ep_theme/Configuration/TSconfig/Page/TCEMAIN.tsconfig b/packages/ep_theme/Configuration/TSconfig/Page/TCEMAIN.tsconfig new file mode 100644 index 0000000..50ea642 --- /dev/null +++ b/packages/ep_theme/Configuration/TSconfig/Page/TCEMAIN.tsconfig @@ -0,0 +1,6 @@ +TCEMAIN.permissions { + groupid = 1 + user = show, editcontent, edit, new, delete + group = show, edit, delete, new, editcontent + everybody = show +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/button.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/button.typoscript new file mode 100644 index 0000000..d7f1e39 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/button.typoscript @@ -0,0 +1,8 @@ +tt_content.button =< lib.contentElement +tt_content.button { + templateName = Button + variables { + referrerPid = TEXT + referrerPid.data = TSFE:id + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/calendar.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/calendar.typoscript new file mode 100644 index 0000000..1684e49 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/calendar.typoscript @@ -0,0 +1,14 @@ +tt_content.calendar =< lib.contentElement +tt_content.calendar { + templateName = Calendar + dataProcessing { + 10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor + 10 { + table = tx_epproducts_domain_model_hotel + where.data = field:tx_eptheme_context_hotel + where.wrap = uid=| + pidInList = 5 + as = hotel + } + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/contact.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/contact.typoscript new file mode 100644 index 0000000..919d6b7 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/contact.typoscript @@ -0,0 +1,23 @@ +tt_content.contact =< lib.contentElement +tt_content.contact { + templateName = Contact + dataProcessing { + 10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor + 10 { + table = tx_epproducts_domain_model_teammember + where.data = field:tx_eptheme_contact + where.wrap = uid=| + pidInList = {$plugin.tx_eptheme.settings.teamMemberStoragePid} + as = contact + + dataProcessing { + 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor + 10 { + references.fieldName = image + references.table = tx_epproducts_domain_model_teammember + as = image + } + } + } + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/container.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/container.typoscript new file mode 100644 index 0000000..4a07634 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/container.typoscript @@ -0,0 +1,43 @@ +tt_content.ep_container_halves =< lib.contentElement +tt_content.ep_container_halves { + templateName = Halves + templateRootPaths { + 10 = EXT:ep_theme/Resources/Private/Templates/Container + } + dataProcessing { + 100 = B13\Container\DataProcessing\ContainerProcessor + } +} + +tt_content.ep_container_thirds =< lib.contentElement +tt_content.ep_container_thirds { + templateName = Thirds + templateRootPaths { + 10 = EXT:ep_theme/Resources/Private/Templates/Container + } + dataProcessing { + 100 = B13\Container\DataProcessing\ContainerProcessor + } +} + +tt_content.ep_container_quarters =< lib.contentElement +tt_content.ep_container_quarters { + templateName = Quarters + templateRootPaths { + 10 = EXT:ep_theme/Resources/Private/Templates/Container + } + dataProcessing { + 100 = B13\Container\DataProcessing\ContainerProcessor + } +} + +tt_content.ep_container_teasergroup =< lib.contentElement +tt_content.ep_container_teasergroup { + templateName = Teasergroup + templateRootPaths { + 10 = EXT:ep_theme/Resources/Private/Templates/Container + } + dataProcessing { + 100 = B13\Container\DataProcessing\ContainerProcessor + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/disrupter.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/disrupter.typoscript new file mode 100644 index 0000000..cffda85 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/disrupter.typoscript @@ -0,0 +1,28 @@ +tt_content.disrupter =< lib.contentElement +tt_content.disrupter { + templateName = Disrupter + dataProcessing { + 10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor + 10 { + table = tx_eptheme_domain_model_disrupter + pidInList = {$plugin.tx_eptheme.settings.disrupterStoragePid} + join = tx_eptheme_ttcontent_tx_eptheme_disrupter_mm ON tx_eptheme_ttcontent_tx_eptheme_disrupter_mm.uid_foreign = tx_eptheme_domain_model_disrupter.uid + where { + data = field:uid + intval = 1 + wrap = tx_eptheme_ttcontent_tx_eptheme_disrupter_mm.uid_local=| + } + as = disrupter + + dataProcessing { + 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor + 10 { + references.fieldName = image + references.table = tx_eptheme_domain_model_disrupter + as = image + } + 20 = EP\EpTheme\DataProcessing\FlexFormProcessor + } + } + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/disturber.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/disturber.typoscript new file mode 100644 index 0000000..d32d0a3 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/disturber.typoscript @@ -0,0 +1,13 @@ +tt_content.deal_of_the_week =< lib.contentElement +tt_content.deal_of_the_week { + templateName = DealOfTheWeek +} + +tt_content.disturber =< lib.contentElement +tt_content.disturber { + templateName = Disturber + dataProcessing { + 10 = EP\EpTheme\DataProcessing\TeaserImageProcessor + 20 = EP\EpTheme\DataProcessing\FlexFormProcessor + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/event_pricetable.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/event_pricetable.typoscript new file mode 100644 index 0000000..94dccc4 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/event_pricetable.typoscript @@ -0,0 +1,10 @@ +tt_content.event_pricetable =< lib.contentElement +tt_content.event_pricetable { + templateName = EventPricetable + variables { + forcePartnerIdSelect = TEXT + forcePartnerIdSelect.value = {$plugin.tx_eptheme.settings.forcePartnerIdSelect} + partnerIdSelectLabel = TEXT + partnerIdSelectLabel.value = {$plugin.tx_eptheme.settings.partnerIdSelectLabel} + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/faq_overview.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/faq_overview.typoscript new file mode 100644 index 0000000..a2496dc --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/faq_overview.typoscript @@ -0,0 +1,17 @@ +tt_content.faq_overview =< lib.contentElement +tt_content.faq_overview { + templateName = FaqOverview + settings { + infoPageUid = {$plugin.tx_eptheme.settings.infoPageUid} + } + dataProcessing { + 10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor + 10 { + table = tx_epproducts_domain_model_faq + where = category != '' + orderBy = category ASC, uid ASC + pidInList = {$plugin.tx_epproducts.settings.faqsStoragePid} + as = faqs + } + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/gmap.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/gmap.typoscript new file mode 100644 index 0000000..c141099 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/gmap.typoscript @@ -0,0 +1,23 @@ +tt_content.gmap =< tt_content.teaser_product +tt_content.gmap { + templateName = Gmap + dataProcessing { + 10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor + 10 { + table = tx_epproducts_domain_model_hotel + where.data = field:tx_eptheme_context_hotel + where.wrap = uid=| + pidInList = {$plugin.tx_epproducts.settings.hotelsStoragePid} + as = hotel + } + 20 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor + 20 { + table = tx_epproducts_domain_model_region + where.data = field:tx_eptheme_context_region + where.wrap = uid=| + pidInList = {$plugin.tx_epproducts.settings.destinationsStoragePid} + recursive = 2 + as = region + } + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/lineup.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/lineup.typoscript new file mode 100644 index 0000000..f5b0872 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/lineup.typoscript @@ -0,0 +1,24 @@ +tt_content.lineup =< lib.contentElement +tt_content.lineup { + templateName = Lineup + dataProcessing { + 10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor + 10 { + table = tx_eptheme_domain_model_lineup + where.data = field:uid + where.wrap = tt_content=| + orderBy = sorting + as = items + + dataProcessing { + 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor + 10 { + references.fieldName = image + references.table = tx_eptheme_domain_model_lineup + as = image + } + } + } + } +} + diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/list.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/list.typoscript new file mode 100644 index 0000000..192f9a7 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/list.typoscript @@ -0,0 +1,4 @@ +tt_content.list.dataProcessing { + 10 = EP\EpTheme\DataProcessing\FlexFormProcessor +} + diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/nlform.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/nlform.typoscript new file mode 100644 index 0000000..6e80f8b --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/nlform.typoscript @@ -0,0 +1,9 @@ +tt_content.nlform =< lib.contentElement +tt_content.nlform { + templateName = NewsletterForm + variables { + formUrl = TEXT + formUrl.wrap = |/buchung/anmeldung_news.php + formUrl.data = site:base + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/teaser.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/teaser.typoscript new file mode 100644 index 0000000..c787fcf --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/teaser.typoscript @@ -0,0 +1,31 @@ +tt_content.teaser =< lib.contentElement +tt_content.teaser { + templateName = Teaser + dataProcessing { + 10 = EP\EpTheme\DataProcessing\ContextProcessor + 20 = EP\EpTheme\DataProcessing\TeaserImageProcessor + } + settings.teaserTextMaxChars = {$plugin.tx_eptheme.settings.teaserTextMaxChars} +} + +tt_content.teaser_country < lib.contentElement +tt_content.teaser_country { + templateName = TeaserCountry + dataProcessing { + 10 = EP\EpTheme\DataProcessing\TeaserProcessor + } + settings.teaserTextMaxChars = {$plugin.tx_eptheme.settings.teaserTextMaxChars} +} + +tt_content.teaser_region =< tt_content.teaser_country +tt_content.teaser_region.templateName = TeaserRegion +tt_content.teaser_city =< tt_content.teaser_country +tt_content.teaser_city.templateName = TeaserCity +tt_content.teaser_hotel =< tt_content.teaser_country +tt_content.teaser_hotel.templateName = TeaserHotel +tt_content.teaser_concept =< tt_content.teaser_country +tt_content.teaser_concept.templateName = TeaserConcept +tt_content.teaser_product =< tt_content.teaser_country +tt_content.teaser_product.templateName = TeaserProduct +tt_content.teaser_booking_link =< tt_content.teaser_product +tt_content.teaser_booking_link.templateName = TeaserBookingLink diff --git a/packages/ep_theme/Configuration/TypoScript/ContentElements/ytvideo.typoscript b/packages/ep_theme/Configuration/TypoScript/ContentElements/ytvideo.typoscript new file mode 100644 index 0000000..2107bc1 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/ContentElements/ytvideo.typoscript @@ -0,0 +1,4 @@ +tt_content.ytvideo =< lib.contentElement +tt_content.ytvideo { + templateName = YoutubeVideo +} diff --git a/packages/ep_theme/Configuration/TypoScript/Extensions/EpProducts.typoscript b/packages/ep_theme/Configuration/TypoScript/Extensions/EpProducts.typoscript new file mode 100644 index 0000000..bf7151a --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Extensions/EpProducts.typoscript @@ -0,0 +1,24 @@ +@import 'EXT:ep_products/Configuration/TypoScript/setup.typoscript' + +plugin.tx_epproducts { + view { + layoutRootPaths.10 = EXT:ep_theme/Resources/Private/Layouts/Product/ + templateRootPaths.10 = EXT:ep_theme/Resources/Private/Templates/ + partialRootPaths.10 = EXT:ep_theme/Resources/Private/Partials/ + } + settings { + teaserTextMaxChars = {$plugin.tx_eptheme.settings.teaserTextMaxChars} + infoPageUid = {$plugin.tx_eptheme.settings.infoPageUid} + defaultAjaxUid = {$plugin.tx_eptheme.settings.defaultAjaxUid} + defaultContactFormPageUid = {$plugin.tx_eptheme.settings.defaultContactFormPageUid} + phoneNumber = {$plugin.tx_eptheme.settings.phoneNumber} + bpnBookingUrlTemplateCode = {$plugin.tx_eptheme.settings.bpnBookingUrlTemplateCode} + showSkipassBadge = {$plugin.tx_eptheme.settings.showSkipassBadge} + ratingWidgetLabel = {$plugin.tx_eptheme.settings.ratingWidgetLabel} + skipassPageUid = {$plugin.tx_eptheme.settings.skipassPageUid} + conceptsPageUid = {$plugin.tx_eptheme.settings.conceptsPageUid} + mapTileUrl = {$plugin.tx_eptheme.settings.mapTileUrl} + mapApiKey = {$plugin.tx_eptheme.settings.mapApiKey} + mapAttribution = {$plugin.tx_eptheme.settings.mapAttribution} + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Extensions/FluidStyledContent.typoscript b/packages/ep_theme/Configuration/TypoScript/Extensions/FluidStyledContent.typoscript new file mode 100644 index 0000000..9e6961f --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Extensions/FluidStyledContent.typoscript @@ -0,0 +1,14 @@ +@import 'EXT:fluid_styled_content/Configuration/TypoScript/setup.typoscript' + +lib.contentElement { + layoutRootPaths.20 = EXT:ep_theme/Resources/Private/Layouts/FluidStyledContent + templateRootPaths.20 = EXT:ep_theme/Resources/Private/Templates/FluidStyledContent + partialRootPaths.20 = EXT:ep_theme/Resources/Private/Partials + settings < plugin.tx_eptheme.settings + settings.themekey = {$plugin.tx_eptheme.settings.themekey} + settings.defaultAjaxUid = {$plugin.tx_eptheme.settings.defaultAjaxUid} + settings.mapTileUrl = {$plugin.tx_eptheme.settings.mapTileUrl} + settings.mapApiKey = {$plugin.tx_eptheme.settings.mapApiKey} + settings.mapAttribution = {$plugin.tx_eptheme.settings.mapAttribution} +} + diff --git a/packages/ep_theme/Configuration/TypoScript/Extensions/Form.typoscript b/packages/ep_theme/Configuration/TypoScript/Extensions/Form.typoscript new file mode 100644 index 0000000..339880c --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Extensions/Form.typoscript @@ -0,0 +1,30 @@ +@import 'EXT:form/Configuration/TypoScript/setup.typoscript' +@import 'EXT:form_element_linked_checkbox/Configuration/TypoScript/setup.typoscript' +@import 'EXT:bw_captcha/Configuration/TypoScript/setup.typoscript' + +plugin.tx_form.view { + templateRootPaths.10 = EXT:ep_theme/Resources/Private/Templates/Form/ +} + +plugin.tx_form.settings.yamlConfigurations { + 1630994710 = EXT:ep_theme/Configuration/Yaml/FormsBase.yaml + 1630994711 = EXT:ep_theme/Configuration/Yaml/FormFinishersFrontend.yaml +} + +module.tx_form.settings.yamlConfigurations { + 1630994710 = EXT:ep_theme/Configuration/Yaml/FormsBase.yaml + 1630994711 = EXT:ep_theme/Configuration/Yaml/FormFinishersBackend.yaml +} + +plugin.tx_bwcaptcha.settings { + charset = abcdefghjkmnpqrtuvwxy34679 + length = 4 + width = 220 + height = 60 +} + +# Requests with a malformed type (e.g. ?type=03413) bypass the bw_captcha middleware's +# strict string check and get rendered as this empty PAGE. If it were cacheable, genuine +# ?type=3413 requests would hit that cache entry, TypoScript would never be parsed and +# the middleware would crash on empty settings. +captcha.config.no_cache = 1 diff --git a/packages/ep_theme/Configuration/TypoScript/Extensions/GridElements.typoscript b/packages/ep_theme/Configuration/TypoScript/Extensions/GridElements.typoscript new file mode 100644 index 0000000..f118b41 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Extensions/GridElements.typoscript @@ -0,0 +1,20 @@ +@import 'EXT:gridelements/Configuration/TypoScript/setup.typoscript' + +tt_content.gridelements_pi1.20.10.setup { + quarters < lib.gridelements.defaultGridSetup + quarters { + cObject = FLUIDTEMPLATE + cObject { + templateName = Quarters + templateRootPaths.0 = EXT:ep_theme/Resources/Private/Templates/Gridelements/ + layoutRootPaths.0 = EXT:ep_theme/Resources/Private/Layouts/Gridelements/ + partialRootPaths.0 = EXT:ep_theme/Resources/Private/Partials/ + } + } + thirds < .quarters + thirds.cObject.templateName = Thirds + halves < .quarters + halves.cObject.templateName = Halves + halfAndTwoQuarters < .quarters + halfAndTwoQuarters.cObject.templateName = HalfAndTwoQuarters +} diff --git a/packages/ep_theme/Configuration/TypoScript/Extensions/News.typoscript b/packages/ep_theme/Configuration/TypoScript/Extensions/News.typoscript new file mode 100644 index 0000000..52a7989 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Extensions/News.typoscript @@ -0,0 +1,13 @@ +@import 'EXT:news/Configuration/TypoScript/setup.txt' + +plugin.tx_news { + view { + layoutRootPaths.100 = EXT:ep_theme/Resources/Private/Layouts/News/ + templateRootPaths.100 = EXT:ep_theme/Resources/Private/Templates/News/ + partialRootPaths.100 = EXT:ep_theme/Resources/Private/Partials/News/ + widget.GeorgRinger\News\ViewHelpers\Widget\PaginateViewHelper.templateRootPath = EXT:ep_theme/Resources/Private/Templates/News/ + } + settings { + tagListPageUid = {$plugin.tx_eptheme.settings.blogTagListPageUid} + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Extensions/Replacer.typoscript b/packages/ep_theme/Configuration/TypoScript/Extensions/Replacer.typoscript new file mode 100644 index 0000000..beba97c --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Extensions/Replacer.typoscript @@ -0,0 +1,26 @@ +config.tx_replacer { + enable_regex = 1 + + # Only images go to the CDN. The theme's own css/js/fonts are served by the production + # host: they are already minified by Webpack, gzipped by mod_deflate and content-hashed + # for a year of immutable browser caching, and keeping them on the page's own origin + # means they arrive over the connection the browser has already opened rather than + # costing a second DNS lookup and TLS handshake on the critical path. + # + # It also keeps them clear of the pull zone's habit of occasionally wedging a single + # cache key (10.0s -> 504 errorcode 106 until that key is purged, while the origin + # serves the same bytes in under 100ms). For an image that degrades one element; for + # js/main..js it would take every visitor's JavaScript down. + # + # typo3temp is scoped to generated images on purpose - assets/compressed holds other + # extensions' concatenated css/js, which belongs on the origin for the same reason. + # The quote character is captured rather than matched literally, so url() values + # written with single quotes are rewritten too. + search { + 10 = /(["'])\/?(fileadmin|uploads|typo3temp\/assets\/(?:_processed_|images))/ + } + + replace { + 10 = $1https://cdn.ep-reisen.de/$2 + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Extensions/Seo.typoscript b/packages/ep_theme/Configuration/TypoScript/Extensions/Seo.typoscript new file mode 100644 index 0000000..3f7ece8 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Extensions/Seo.typoscript @@ -0,0 +1,18 @@ +@import 'EXT:seo/Configuration/TypoScript/XmlSitemap/setup.typoscript' + +["{$plugin.tx_eptheme.settings.bookingLinksPid}" > "0"] +plugin.tx_seo { + config { + xmlSitemap { + sitemaps { + pages { + config { + excludedDoktypes = 4,1701,1702,1803 + additionalWhere = AND pid NOT IN ({$plugin.tx_eptheme.settings.bookingLinksPid}) AND no_index = 0 + } + } + } + } + } +} +[END] diff --git a/packages/ep_theme/Configuration/TypoScript/Extensions/Typo3Encore.typoscript b/packages/ep_theme/Configuration/TypoScript/Extensions/Typo3Encore.typoscript new file mode 100644 index 0000000..0a5e490 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Extensions/Typo3Encore.typoscript @@ -0,0 +1,10 @@ +@import 'EXT:typo3_encore/Configuration/TypoScript/setup.typoscript' + +plugin.tx_typo3encore { + settings { + builds { + EpThemeConfig = EXT:ep_theme/Resources/Public + } + preload.enable = 0 + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Library/Breadcrumb.typoscript b/packages/ep_theme/Configuration/TypoScript/Library/Breadcrumb.typoscript new file mode 100644 index 0000000..145490e --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Library/Breadcrumb.typoscript @@ -0,0 +1,13 @@ +lib.breadcrumb = HMENU +lib.breadcrumb { + special = rootline + special.range = 0|-1 + 1 = TMENU + 1.wrap = + 1.NO { + stdWrap.htmlSpecialChars = 1 + ATagTitle.field = nav_title // title + linkWrap = |/|*||*| | + doNotLinkIt = |*| 0 |*| 1 + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Library/DynamicContent.typoscript b/packages/ep_theme/Configuration/TypoScript/Library/DynamicContent.typoscript new file mode 100644 index 0000000..99c580a --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Library/DynamicContent.typoscript @@ -0,0 +1,49 @@ +lib.dynamicContent = COA +lib.dynamicContent { + 5 = LOAD_REGISTER + 5 { + colPos.cObject = TEXT + colPos.cObject { + field = colPos + ifEmpty.cObject = TEXT + ifEmpty.cObject { + value.current = 1 + ifEmpty = 0 + } + } + pageUid.cObject = TEXT + pageUid.cObject { + field = pageUid + ifEmpty.data = TSFE:id + } + contentFromPid.cObject = TEXT + contentFromPid.cObject { + data = DB:pages:{register:pageUid}:content_from_pid + data.insertData = 1 + } + wrap.cObject = TEXT + wrap.cObject { + field = wrap + } + } + 20 = CONTENT + 20 { + table = tt_content + select { + includeRecordsWithoutDefaultTranslation = 1 + orderBy = sorting + where = colPos={register:colPos} + where.insertData = 1 + pidInList.data = register:pageUid + pidInList.override.data = register:contentFromPid + } + stdWrap { + dataWrap = {register:wrap} + required = 1 + } + } + 90 = RESTORE_REGISTER +} + +lib.dynamicContentSlide =< lib.dynamicContent +lib.dynamicContentSlide.20.slide = -1 diff --git a/packages/ep_theme/Configuration/TypoScript/Library/ParseFunc.typoscript b/packages/ep_theme/Configuration/TypoScript/Library/ParseFunc.typoscript new file mode 100644 index 0000000..7469f5f --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Library/ParseFunc.typoscript @@ -0,0 +1,124 @@ +lib.parseFunc { + makelinks = 1 + makelinks { + http { + keep = path + extTarget = _blank + } + mailto { + keep = path + } + } + tags { + link = TEXT + link { + current = 1 + typolink { + parameter { + data = parameters : allParams + } + extTarget = _blank + } + parseFunc.constants = 1 + } + a = TEXT + a { + current = 1 + typolink { + parameter.data = parameters:href + title.data = parameters:title + ATagParams.data = parameters:allParams + target.data = parameters:target + extTarget = _blank + extTarget.override.data = parameters:target + } + } + } + allowTags := addToList(a, abbr, acronym, address, article, aside, b, bdo) + allowTags := addToList(big, blockquote, br, caption, center, cite, code, col) + allowTags := addToList(colgroup, dd, del, dfn, dl, div, dt, em, font) + allowTags := addToList(footer, header, h1, h2, h3, h4, h5, h6, hr, i, img) + allowTags := addToList(ins, kbd, label, li, link, meta, nav, ol, p, pre, q) + allowTags := addToList(samp, sdfield, section, small, span, strike, strong) + allowTags := addToList(style, sub, sup, table, thead, tbody, tfoot, td, th) + allowTags := addToList(tr, title, tt, u, ul, var) + denyTags = * + sword = | + constants = 1 + nonTypoTagStdWrap { + HTMLparser = 1 + HTMLparser { + keepNonMatchedTags = 1 + htmlSpecialChars = 2 + } + } +} + + +lib.parseFunc_RTE < lib.parseFunc +lib.parseFunc_RTE { + externalBlocks := addToList(article, aside, blockquote, div, dd, dl, footer) + externalBlocks := addToList(header, nav, ol, section, table, ul, pre, svg, use) + externalBlocks { + ol { + stripNL = 1 + stdWrap { + parseFunc = < lib.parseFunc + } + } + ul { + stripNL = 1 + stdWrap { + parseFunc = < lib.parseFunc + } + } + table { + stripNL = 1 + stdWrap { + wrap =
|
+ } + callRecursive = 1 + callRecursive.tagStdWrap.HTMLparser = 1 + callRecursive.tagStdWrap.HTMLparser.tags.table { + fixAttrib.class.default = w-full + } + HTMLtableCells = 1 + HTMLtableCells { + default.stdWrap { + parseFunc = < lib.parseFunc_RTE + parseFunc { + nonTypoTagStdWrap { + encapsLines { + nonWrappedTag = + } + } + } + } + } + } + pre { + stripNL = 1 + } + div { + stripNL = 1 + callRecursive = 1 + } + blockquote < .div + article < .div + aside < .div + footer < .div + header < .div + nav < .div + section < .div + dl < .div + dd < .div + } + nonTypoTagStdWrap.encapsLines > + nonTypoTagStdWrap { + HTMLparser = 1 + HTMLparser { + keepNonMatchedTags = 1 + htmlSpecialChars = 2 + } + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Library/SearchBar.typoscript b/packages/ep_theme/Configuration/TypoScript/Library/SearchBar.typoscript new file mode 100644 index 0000000..186217f --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Library/SearchBar.typoscript @@ -0,0 +1,21 @@ +lib.searchBar = USER +lib.searchBar { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = searchbar + vendorName = EP + controller = Searchbar + action = index + switchableControllerActions { + Searchbar { + 1 = index + } + } + settings =< plugin.tx_epproducts.settings + settings.searchPageUid < plugin.tx_epproducts.settings.defaultSearchPageUid + persistence =< plugin.tx_epproducts.persistence + view =< plugin.tx_epproducts.view +} + +lib.searchBarCollapsed < lib.searchBar +lib.searchBarCollapsed.settings.collapsed = 1 diff --git a/packages/ep_theme/Configuration/TypoScript/Library/Sitemap.typoscript b/packages/ep_theme/Configuration/TypoScript/Library/Sitemap.typoscript new file mode 100644 index 0000000..384d7e6 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Library/Sitemap.typoscript @@ -0,0 +1,135 @@ +lib.tue_sitemapcsv = COA_INT +lib.tue_sitemapcsv { + 5 = TEXT + 5 { + value ( +"URL";"Latest change";"Doktype";"Title";"Nav Title";"Description" + ) + } + 10 = COA + 10 { + 10 = TEXT + 10 { + typolink { + forceAbsoluteUrl = 1 + parameter.field = uid + returnLast = url + } + wrap = "|"; + replacement.10 { + search = " + replace = "" + } + } + 20 = CONTENT + 20 { + table = tt_content + select { + pidInList.field = uid + orderBy = tstamp DESC + languageField = sys_language_uid + max = 1 + } + renderObj = COA + renderObj { + 10 = TEXT + 10 { + field = tstamp + date = c + postCObject = TEXT + postCObject { + field = tstamp + age = " Minutes| Hours| Days| Years| Minute| Hour| Day| Year" + noTrimWrap = | (vor |)| + } + if { + value.field = tstamp + isLessThan.data = DB:pages:{field:uid}:tstamp + isLessThan.data.insertData = 1 + } + } + } + stdWrap { + ifEmpty.cObject < lib.tue_sitemapcsv.10.20.renderObj.10 + ifEmpty.cObject.if > + replacement < lib.tue_sitemapcsv.10.10.replacement + wrap < lib.tue_sitemapcsv.10.10.wrap + } + } + 30 = CASE + 30 { + key.field = doktype + 1 = TEXT + 1.value = Standard + 3 = TEXT + 3.value = External URL + 4 = TEXT + 4.value = Shortcut + 6 = TEXT + 6.value = Backend User Section + stdWrap { + replacement < lib.tue_sitemapcsv.10.10.replacement + wrap < lib.tue_sitemapcsv.10.10.wrap + } + } + 40 = TEXT + 40 { + field = title + replacement < lib.tue_sitemapcsv.10.10.replacement + wrap < lib.tue_sitemapcsv.10.10.wrap + } + 50 = TEXT + 50 { + field = nav_title + replacement < lib.tue_sitemapcsv.10.10.replacement + wrap < lib.tue_sitemapcsv.10.10.wrap + } + 60 = TEXT + 60 { + field = description + replacement < lib.tue_sitemapcsv.10.10.replacement + wrap < lib.tue_sitemapcsv.10.10.wrap + } + 100 = TEXT + 100 { + value ( + + + ) + } + } + 20 = HMENU + 20 { + includeNotInMenu = 1 + 1 = TMENU + 1 { + noBlur = 1 + expAll = 1 + NO { + doNotShowLink = 1 + allStdWrap.cObject < lib.tue_sitemapcsv.10 + } + } + 2 < .1 + 3 < .1 + 4 < .1 + 5 < .1 + 6 < .1 + } +} + +[backend.user.isLoggedIn] + sitemapcsv = PAGE + sitemapcsv { + typeNum = 888 + config { + disableAllHeaderCode = 1 + xhtml_cleaning = 0 + admPanel = 0 + debug = 0 + additionalHeaders = Content-type:text/csv;charset=utf-8|Content-Disposition:attachment; filename="sitemap.csv" + } + 10 < lib.tue_sitemapcsv + } +[END] + diff --git a/packages/ep_theme/Configuration/TypoScript/Page/Ajax.typoscript b/packages/ep_theme/Configuration/TypoScript/Page/Ajax.typoscript new file mode 100644 index 0000000..aec8b87 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Page/Ajax.typoscript @@ -0,0 +1,30 @@ +ajax_page_json = PAGE +ajax_page_json { + typeNum = 1803 + config { + disableAllHeaderCode = 1 + additionalHeaders.10.header = Content-type:application/json + xhtml_cleaning = 0 + admPanel = 0 + debug = 0 + } + + 10 = USER_INT + 10 { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpTheme + pluginName = Ajax + vendorName = EP + controller = AjaxForm + action = processForm + switchableControllerActions { + AjaxForm { + 1 = processForm + } + } + features.requireCHashArgumentForActionArguments = 0 + settings =< plugin.tx_eptheme.settings + persistence =< plugin.tx_eptheme.persistence + view =< plugin.tx_eptheme.view + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Page/ContentOnly.typoscript b/packages/ep_theme/Configuration/TypoScript/Page/ContentOnly.typoscript new file mode 100644 index 0000000..fcf688d --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Page/ContentOnly.typoscript @@ -0,0 +1,62 @@ +# Chatbot content feed: the main content of a page, without header, navigation, footer or +# assets. Fetched by MyE&P for pages the content manifest reports as changed; protected by +# EP\EpProducts\Middleware\ContentPageTypeGuardMiddleware. See +# .ddev/plans/CHATBOT-MCP-01-TYPO3-CONTENT-FEED.md §6. + +# Plugins (the booking widget on product detail pages), forms, sub-page menus and teaser cards +# are left out: MyE&P has trip data first-hand, and the pages and records the cards link to are +# in the feed in their own right. The filter applies to top-level elements only; a plugin nested +# in a grid or container element is still rendered by its parent. +lib.contentOnly < lib.dynamicContent +lib.contentOnly.20.select.where = colPos={register:colPos} AND CType NOT IN ({$plugin.tx_eptheme.settings.contentOnlyExcludedCTypes}) + +tx_eptheme_content_only = PAGE +tx_eptheme_content_only { + typeNum = 1730 + config { + disableAllHeaderCode = 1 + disablePrefixComment = 1 + additionalHeaders.10.header = Content-Type: text/html; charset=utf-8 + additionalHeaders.20.header = X-Robots-Tag: noindex, nofollow, noarchive + admPanel = 0 + debug = 0 + # Never written to disk: the type is key-protected, and a static file would be served + # by .htaccess without that check ever running. + tx_staticfilecache.disableCache = 1 + # Same reason for shared caches between us and the consumer: the TYPO3 page cache is + # fine (the guard runs before it), a proxy storing a keyed response is not. + sendCacheHeaders = 0 + additionalHeaders.30.header = Cache-Control: private, no-store + } + + 10 = COA + 10 { + wrap =
|
+ + 10 = TEXT + 10.field = title + 10.htmlSpecialChars = 1 + 10.wrap =

|

+ + # Page templates without a sidebar render colPos 0; the sidebar layouts render 3 (header), + # 4 (content) and 2 (sidebar). colPos 1 holds teaser groups and is left out. The page + # templates pass colPos in as data; here there is no template, so each copy sets it. + # These must be copies (<), not references (=<): removing colPos.cObject on a reference + # leaves the referenced cObject in place, and every column silently renders colPos 0. + 20 < lib.contentOnly + 20.5.colPos.cObject > + 20.5.colPos = 3 + + 30 < lib.contentOnly + 30.5.colPos.cObject > + 30.5.colPos = 0 + + 40 < lib.contentOnly + 40.5.colPos.cObject > + 40.5.colPos = 4 + + 50 < lib.contentOnly + 50.5.colPos.cObject > + 50.5.colPos = 2 + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Page/Export.typoscript b/packages/ep_theme/Configuration/TypoScript/Page/Export.typoscript new file mode 100644 index 0000000..333c2a3 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Page/Export.typoscript @@ -0,0 +1,25 @@ +xmlExport = PAGE +xmlExport { + typeNum = 1972 + config { + xhtml_cleaning = none + disableAllHeaderCode = 1 + additionalHeaders = Content-Type: text/xml; charset=utf-8 + simulateStaticDocuments = 0 + admPanel = 0 + debug = 0 + tx_realurl_enable = 1 + } + 10 = USER_INT + 10 { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = reseller + vendorName = EP + switchableControllerActions { + Reseller { + 1 = xml + } + } + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Page/Main.typoscript b/packages/ep_theme/Configuration/TypoScript/Page/Main.typoscript new file mode 100644 index 0000000..42aacca --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Page/Main.typoscript @@ -0,0 +1,214 @@ +page = PAGE +page { + meta { + viewport = width=device-width, initial-scale=1 + X-UA-Compatible = ie=edge + format-detection = telephone=no + } + + bodyTagCObject = COA + bodyTagCObject { + 10 = TEXT + 10.value = + } + + # Every image on the page comes from the CDN host (see Extensions/Replacer.typoscript), + # so a cold visitor pays a full DNS + TCP + TLS handshake - measured at 50-70ms - before + # the first one can start downloading. `preconnect` gets that out of the way while the + # HTML is still parsing. No `crossorigin`: images are fetched without CORS, and an + # anonymous-vs-CORS mismatch would open a second connection instead of reusing this one. + # Fonts, which would need it, are served by this host now. + headerData.100 = TEXT + headerData.100.value = + + headerData.333 = TEXT + headerData.333.value ( + + + + + + + + + + + + + + + + + + + + + + + ) + + # disableCompression keeps ResourceCompressor's hands off the Encore entries: Webpack + # already minifies them, and copying them to typo3temp/assets/compressed only replaces the + # content hash with a name derived from md5(abspath + mtime + size), which changes on every + # deploy even when the bytes do not - defeating the immutable caching the hash exists for. + # RequestHandler turns this into `compress => false`, which PageRendererHooks forwards to + # typo3_encore's TagRenderer (whose own default it would otherwise override). + includeCSS { + main = typo3_encore:EpThemeConfig:main + main.disableCompression = 1 + } + + includeJSFooter { + main = typo3_encore:EpThemeConfig:main + main.defer = 1 + main.disableCompression = 1 + } + + 10 = FLUIDTEMPLATE + 10 { + file { + cObject = TEXT + cObject { + data = levelfield: -1, backend_layout_next_level, slide + override { + data = TSFE:page|backend_layout + } + + ifEmpty = Default + split { + token = pagets__ + 1.current = 1 + 1.wrap = | + } + + wrap = typo3conf/ext/ep_theme/Resources/Private/Templates/Page/|.html + case = ucfirst + } + } + + layoutRootPaths.0 = EXT:ep_theme/Resources/Private/Layouts/ + partialRootPaths.0 = EXT:ep_theme/Resources/Private/Partials/ + templateRootPaths.0 = EXT:ep_theme/Resources/Private/Templates/ + + dataProcessing { + 10 = EP\EpTheme\DataProcessing\ContextProcessor + 20 = EP\EpTheme\DataProcessing\SliderProcessor + 25 = EP\EpTheme\DataProcessing\ProductRatingProcessor + 30 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor + 30 { + table = tx_epproducts_domain_model_badge + where.data = field:tx_eptheme_badge + where.wrap = uid=| + pidInList = {$plugin.tx_eptheme.settings.badgeStoragePid} + as = badge + if { + equals.field = backend_layout + value = pagets__detail + negate = 1 + } + } + 40 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor + 40 { + special = directory + special.value = {$plugin.tx_eptheme.settings.topNavPid} + levels = 1 + as = topnav + } + 50 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor + 50 { + special = directory + special.value = {$plugin.tx_eptheme.settings.mainNavPid} + levels = 3 + titleField = title + as = mainnav + } + 60 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor + 60 { + special = directory + special.value = {$plugin.tx_eptheme.settings.mobileNavPid} + levels = 4 + expandAll = 1 + titleField = title + as = mobilenav + } + 70 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor + 70 { + special = directory + special.value = {$plugin.tx_eptheme.settings.footerNavPid} + levels = 2 + expandAll = 1 + titleField = title + as = footernav + } + 80 = TYPO3\CMS\Frontend\DataProcessing\SiteProcessor + 80 { + as = site + } + } + settings < plugin.tx_eptheme.settings + } + + headerData.990 = TEXT + headerData.990 { + value = + if.isTrue = {$plugin.tx_eptheme.settings.cookieFirstScriptUrl} + } + + headerData.995 = TEXT + headerData.995 { + value ( + + + + ) + if.isTrue = {$plugin.tx_eptheme.settings.googleTagManagerId} + } +} + +[page["backend_layout"] == 'pagets__popup'] + page.bodyTagCObject > + page.bodyTagCObject = TEXT + page.bodyTagCObject.value = + page.headerData.333 > +# page.headerData.990 > +# page.headerData.995 > +[end] + +[page["backend_layout"] == 'pagets__travelinfo'] + page.bodyTagCObject > + page.bodyTagCObject = TEXT + page.bodyTagCObject.value = +[end] + +[request.getNormalizedParams() && like(request.getNormalizedParams().getHttpUserAgent(), "*Googlebot*")] + page.headerData.990 > + page.headerData.995 > +[end] + +#[applicationContext == "Development" || applicationContext == "Production/Staging"] +# page.headerData.990 > +# page.headerData.995 > +#[end] diff --git a/packages/ep_theme/Configuration/TypoScript/constants.typoscript b/packages/ep_theme/Configuration/TypoScript/constants.typoscript new file mode 100644 index 0000000..db6d116 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/constants.typoscript @@ -0,0 +1,202 @@ +# customsubcategory=100=Social links +# customsubcategory=200=Navigations and page UIDs +# customsubcategory=300=Google related +# customsubcategory=400=Contact data +# customsubcategory=500=Template +# customsubcategory=600=XML Sitemap +# customsubcategory=700=Event dates +# customsubcategory=800=Badges +# customsubcategory=900=MyE&P config + +plugin.tx_eptheme { + settings { + # cat=eptheme/200/10; type=string; label=Theme key + themekey = ep + # cat=eptheme/200/100; type=string; label=Top navigation PID + topNavPid = + # cat=eptheme/200/110; type=string; label=Main navigation PID + mainNavPid = + # cat=eptheme/200/115; type=string; label=Mobile navigation PID + mobileNavPid = + # cat=eptheme/200/120; type=string; label=Footer navigation PID + footerNavPid = + # cat=eptheme/200/130; type=string; label=Default homepage UID + defaultHomeUid = + # cat=eptheme/200/131; type=string; label=Main site PID + mainSitePid = + # cat=eptheme/200/135; type=string; label=Link to apply to logo (overrides defaultHomeUid) + logoLink = + # cat=eptheme/200/140; type=string; label=Default page UID for ajax requests + defaultAjaxUid = + # cat=eptheme/200/150; type=string; label=Default contact form page UID + defaultContactFormPageUid = + # cat=eptheme/200/160; type=string; label=Legal notice page UID + legalNoticePageUid = + # cat=eptheme/200/170; type=string; label=Terms and conditions page UID + termsAndConditionsPageUid = + # cat=eptheme/200/180; type=string; label=Data protection page UID + dataProtectionPageUid = + # cat=eptheme/200/185; type=string; label=Image credits page UID + imageCreditsPageUid = + # cat=eptheme/200/190; type=string; label=Cookie policy page UID + cookiePolicyPageUid = + # cat=eptheme/200/200; type=string; label=FAQ page UID + faqPageUid = + # cat=eptheme/200/210; type=string; label=Info page UID + infoPageUid = + # cat=eptheme/200/220; type=string; label=Groups page UID + groupsPageUid = + # cat=eptheme/200/225; type=string; label=Blog detail page UID + blogDetailPageUid = + # cat=eptheme/200/230; type=string; label=Blog tag list page UID + blogTagListPageUid = + # cat=eptheme/200/240; type=string; label=Groups contact form page UID + groupsContactFormPageUid = + # cat=eptheme/200/250; type=string; label=Rating page UID + ratingPageUid = + # cat=eptheme/200/260; type=string; label=Watchlist page UID + watchlistPageUid = + # cat=eptheme/200/270; type=string; label=Skipass info page UID + skipassPageUid = + # cat=eptheme/200/280; type=string; label=English page UID + englishPageUid = + # cat=eptheme/200/285; type=string; label=German page UID + germanPageUid = + # cat=eptheme/200/290; type=string; label=Concepts page UID + conceptsPageUid = + # cat=eptheme/200/300; type=string; label=My E&P page UID + myEpPageUid = + # cat=eptheme/200/310; type=string; label=Booking links PID + bookingLinksPid = + # cat=eptheme/200/330; type=string; label=Groups price popup page UID + groupsPricePopupPageUid = + # cat=eptheme/200/340; type=string; label='Reisen Alert' page UID + travelAlertPageUid = + # cat=eptheme/200/350; type=string; label='Corona Updates' page UID + coronaUpdatesPageUid = + + # cat=eptheme/500/100; type=string; label=Map tile URL template (without apiKey) + mapTileUrl = https://api-eu.geoapify.com/v1/tile/osm-bright/{z}/{x}/{y}.png + # cat=eptheme/500/110; type=string; label=Map tile API key (Geoapify), restricted by referrer + mapApiKey = + # cat=eptheme/500/120; type=string; label=Map attribution (Geoapify and OpenStreetMap credits are both required) + mapAttribution = Powered by Geoapify | © OpenStreetMap contributors + + # cat=eptheme/400/100; type=string; label=Phone number general + phoneNumber = 0221 - 272 276 0 + # cat=eptheme/400/110; type=string; label=Phone number WhatsApp + whatsApp = 0221 - 272 276 0 + # cat=eptheme/400/100; type=string; label=Phone number general + phoneNumberLink = +492212722760 + # cat=eptheme/400/110; type=string; label=Phone number WhatsApp + whatsAppNumberLink = https://wa.me/492212722760 + # cat=eptheme/400/120; type=string; label=Business hours days + businessHoursDays = Montag - Freitag + # cat=eptheme/400/130; type=string; label=Business hours time + businessHoursTime = 08:30 bis 18:30 + # cat=eptheme/400/140; type=string; label=Contact form email to + contactFormToEmail = + # cat=eptheme/400/150; type=string; label=Contact form name to + contactFormToName = + # cat=eptheme/400/160; type=boolean; label=Enable contact form autoreply + enableContactFormAutoreply = 0 + # cat=eptheme/400/170 type=string; label=Contact form autoreply subject + contactFormAutoreplySubject = Ihre Anfrage bei E&P Reisen + + # cat=eptheme/100/100; type=string; label=Facebook URL + facebookUrl = + # cat=eptheme/100/120; type=string; label=Instagram URL + instagramUrl = + # cat=eptheme/100/130; type=string; label=YouTube URL + youtubeUrl = + # cat=eptheme/100/135; type=string; label=tiktok URL + tiktokUrl = https://www.tiktok.com/@ep_reisen + # cat=eptheme/100/140; type=string; label=Facebook ViewContent 'product uid' + facebookViewContentProductUid = + # cat=eptheme/100/150; type=string; label=Facebook ViewContent 'name internal' + facebookViewContentNameInternal = + + # cat=eptheme; type=string; label=Slider storage PID + sliderStoragePid = + # cat=eptheme; type=string; label=Teammember storage PID + teamMemberStoragePid = + # cat=eptheme; type=string; label=Disrupter storage PID + disrupterStoragePid = + + # cat=eptheme/500/100; type=string; label=Teasertext max characters + teaserTextMaxChars = 120 + # cat=eptheme/500/110; type=string; label=Default bodyclass + defaultBodyclass = subpage + # cat=eptheme/500/120; type=string; label=Logo image + logoImage = EXT:ep_theme/Resources/Public/images/logo_wide.svg + # cat=eptheme/500/130; type=string; label=Force active topnav uid + forceActiveTopnavUid = + # cat=eptheme/500/140; type=string; label=Booking URL template code + bpnBookingUrlTemplateCode = base + # cat=eptheme/500/150; type=string; label=Search page header image + searchPageHeaderImage = + # cat=eptheme/500/160; type=string; label=Rating widget label + ratingWidgetLabel = Reisebewertung + # cat=eptheme/500/170; type=boolean; label=Hide area/top navigation + hideAreaNavigation = 0 + # cat=eptheme/500/170; type=boolean; label=Hide searchbar + hideSearchbar = 0 + # cat=eptheme/500/180; type=string; label=Global concept code + globalConceptCode = + + # cat=eptheme/800/100; type=boolean; label=Show 'skipass inklusive' badge + showSkipassBadge = 1 + # cat=eptheme/800/100; type=string; label=Custom badge storage PID + badgeStoragePid = + + # cat=eptheme/600/100; type=string; label=XML sitemap root page UID + xmlSitemapRootPageUid = + # cat=eptheme/600/110; type=string; label=XML sitemap page UIDs to exclude + xmlSitemapExcludeUids = + # cat=eptheme/600/120; type=string; label=CTypes left out of the chatbot content-only page type (quoted, comma-separated) + contentOnlyExcludedCTypes = 'list','form_formframework','menu_subpages','teaser_product','teaser_hotel','teaser_concept','teaser_country','teaser_region','ep_container_teasergroup' + + # cat=eptheme/300/100; type=string; label=Cookiefirst Script + cookieFirstScriptUrl = https://consent.cookiefirst.com/sites/ep-reisen.de-fabb1554-8932-4075-85dc-97fa68892918/consent.js + # cat=eptheme/300/110; type=string; label=Google Static Maps Base Url + googleStaticMapsBaseUrl = https://maps.googleapis.com/maps/api/staticmap?key=AIzaSyDDOforD1O_j4xXy2nJybC2yQqbUAw9ClY + # cat=eptheme/300/120; type=string; label=Google Maps Js API Url + googleMapsJsApiUrl = https://maps.googleapis.com/maps/api/js?key=AIzaSyDDOforD1O_j4xXy2nJybC2yQqbUAw9ClY + # cat=eptheme/300/120; type=string; label=Google Maps Js API Url + googleMapsApiKey = AIzaSyDDOforD1O_j4xXy2nJybC2yQqbUAw9ClY + # cat=eptheme/300/130; type=string; label=Canonical base URL/Domain + canonicalBaseUrl = https://www.ep-reisen.de + + # cat=eptheme.events/700/100; type=string; label=Event date day from + eventDateDayFrom = + # cat=eptheme.events/700/110; type=string; label=Event date day to + eventDateDayTo = + # cat=eptheme.events/700/120; type=string; label=Event date month + eventDateMonth = + + # cat=eptheme.events; type=string; label=Event herounit claim + eventHeroClaim = + # cat=eptheme.events; type=string; label=Event herounit description + eventHeroDescription = + # cat=eptheme.events; type=string; label=Event disturber headline + eventDisturberHeadline = Absolut
Unschlagbarer
Preis + # cat=eptheme.events; type=string; label=Event disturber list items + eventDisturberListitems = 7 Übernachtungen in Top Apartments;6 Tage Skipass Val Thoerens;Ski & Boarderweek Event-Ticket + # cat=eptheme.events; type=string; label=Event disturber button label + eventDisturberButtonLabel = Jetzt ab 249€ buchen + # cat=eptheme.events; type=string; label=Event disturber button link + eventDisturberButtonLink = + # cat=eptheme.events; type=boolean; label=Force Partner ID selection + forcePartnerIdSelect = + # cat=eptheme.events; type=string; label=Partner ID selection label + partnerIdSelectLabel = + } +} + +@import 'EXT:fluid_styled_content/Configuration/TypoScript/constants.typoscript' +@import 'EXT:ep_products/Configuration/TypoScript/constants.typoscript' +@import 'EXT:gridelements/Configuration/TypoScript/constants.typoscript' +@import 'EXT:seo/Configuration/TypoScript/XmlSitemap/constants.typoscript' +@import 'EXT:typo3_encore/Configuration/TypoScript/constants.typoscript' +@import 'EXT:news/Configuration/TypoScript/constants.txt' +@import 'EXT:bw_captcha/Configuration/TypoScript/constants.typoscript' diff --git a/packages/ep_theme/Configuration/TypoScript/setup.typoscript b/packages/ep_theme/Configuration/TypoScript/setup.typoscript new file mode 100644 index 0000000..3c8025e --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/setup.typoscript @@ -0,0 +1,125 @@ +@import 'EXT:ep_theme/Configuration/TypoScript/Extensions/*' +@import 'EXT:ep_theme/Configuration/TypoScript/Library/*' +@import 'EXT:ep_theme/Configuration/TypoScript/ContentElements/*' + +plugin.tx_eptheme { + settings < lib.contentElement.settings + settings { + themekey = {$plugin.tx_eptheme.settings.themekey} + footerNavPid = {$plugin.tx_eptheme.settings.footerNavPid} + mainNavPid = {$plugin.tx_eptheme.settings.mainNavPid} + mobileNavPid = {$plugin.tx_eptheme.settings.mobileNavPid} + topNavPid = {$plugin.tx_eptheme.settings.topNavPid} + legalNoticePageUid = {$plugin.tx_eptheme.settings.legalNoticePageUid} + termsAndConditionsPageUid = {$plugin.tx_eptheme.settings.termsAndConditionsPageUid} + dataProtectionPageUid = {$plugin.tx_eptheme.settings.dataProtectionPageUid} + imageCreditsPageUid = {$plugin.tx_eptheme.settings.imageCreditsPageUid} + cookiePolicyPageUid = {$plugin.tx_eptheme.settings.cookiePolicyPageUid} + infoPageUid = {$plugin.tx_eptheme.settings.infoPageUid} + groupsPageUid = {$plugin.tx_eptheme.settings.groupsPageUid} + facebookUrl = {$plugin.tx_eptheme.settings.facebookUrl} + youtubeUrl = {$plugin.tx_eptheme.settings.youtubeUrl} + tiktokUrl = {$plugin.tx_eptheme.settings.tiktokUrl} + instagramUrl = {$plugin.tx_eptheme.settings.instagramUrl} + phoneNumber = {$plugin.tx_eptheme.settings.phoneNumber} + whatsApp = {$plugin.tx_eptheme.settings.whatsApp} + phoneNumberLink = {$plugin.tx_eptheme.settings.phoneNumberLink} + whatsAppNumberLink = {$plugin.tx_eptheme.settings.whatsAppNumberLink} + businessHoursDays = {$plugin.tx_eptheme.settings.businessHoursDays} + businessHoursTime = {$plugin.tx_eptheme.settings.businessHoursTime} + teaserTextMaxChars = {$plugin.tx_eptheme.settings.teaserTextMaxChars} + faqPageUid = {$plugin.tx_eptheme.settings.faqPageUid} + defaultHomeUid = {$plugin.tx_eptheme.settings.defaultHomeUid} + mainSitePid = {$plugin.tx_eptheme.settings.mainSitePid} + logoLink = {$plugin.tx_eptheme.settings.logoLink} + defaultAjaxUid = {$plugin.tx_eptheme.settings.defaultAjaxUid} + defaultContactFormPageUid = {$plugin.tx_eptheme.settings.defaultContactFormPageUid} + defaultSearchPageUid = {$plugin.tx_epproducts.settings.defaultSearchPageUid} + defaultDetailPageUid = {$plugin.tx_epproducts.settings.defaultDetailPageUid} + coronaUpdatesPageUid = {$plugin.tx_eptheme.settings.coronaUpdatesPageUid} + logoImage = {$plugin.tx_eptheme.settings.logoImage} + forceActiveTopnavUid = {$plugin.tx_eptheme.settings.forceActiveTopnavUid} + eventDateDayFrom = {$plugin.tx_eptheme.settings.eventDateDayFrom} + eventDateDayTo = {$plugin.tx_eptheme.settings.eventDateDayTo} + eventDateMonth = {$plugin.tx_eptheme.settings.eventDateMonth} + eventHeroClaim = {$plugin.tx_eptheme.settings.eventHeroClaim} + eventHeroDescription = {$plugin.tx_eptheme.settings.eventHeroDescription} + eventDisturberHeadline = {$plugin.tx_eptheme.settings.eventDisturberHeadline} + eventDisturberListitems = {$plugin.tx_eptheme.settings.eventDisturberListitems} + eventDisturberButtonLabel = {$plugin.tx_eptheme.settings.eventDisturberButtonLabel} + eventDisturberButtonLink = {$plugin.tx_eptheme.settings.eventDisturberButtonLink} + cookieFirstScriptUrl = {$plugin.tx_eptheme.settings.cookieFirstScriptUrl} + bpnBookingUrlTemplateCode = {$plugin.tx_eptheme.settings.bpnBookingUrlTemplateCode} + groupsPricePopupPageUid = {$plugin.tx_eptheme.settings.groupsPricePopupPageUid} + googleStaticMapsBaseUrl = {$plugin.tx_eptheme.settings.googleStaticMapsBaseUrl} + googleMapsJsApiUrl = {$plugin.tx_eptheme.settings.googleMapsJsApiUrl} + googleMapsApiKey = {$plugin.tx_eptheme.settings.googleMapsApiKey} + datePickerPresets < plugin.tx_epproducts.settings.datePickerPresets + searchPageHeaderImage = {$plugin.tx_eptheme.settings.searchPageHeaderImage} + ratingWidgetLabel = {$plugin.tx_eptheme.settings.ratingWidgetLabel} + hideAreaNavigation = {$plugin.tx_eptheme.settings.hideAreaNavigation} + hideSearchbar = {$plugin.tx_eptheme.settings.hideSearchbar} + showSkipassBadge = {$plugin.tx_eptheme.settings.showSkipassBadge} + forcePartnerIdSelect = {$plugin.tx_eptheme.settings.forcePartnerIdSelect} + contactFormToEmail = {$plugin.tx_eptheme.settings.contactFormToEmail} + contactFormToName = {$plugin.tx_eptheme.settings.contactFormToName} + enableContactFormAutoreply = {$plugin.tx_eptheme.settings.enableContactFormAutoreply} + contactFormAutoreplySubject = {$plugin.tx_eptheme.settings.contactFormAutoreplySubject} + groupsContactFormPageUid = {$plugin.tx_eptheme.settings.groupsContactFormPageUid} + blogDetailPageUid = {$plugin.tx_eptheme.settings.blogDetailPageUid} + ratingPageUid = {$plugin.tx_eptheme.settings.ratingPageUid} + watchlistPageUid = {$plugin.tx_eptheme.settings.watchlistPageUid} + skipassPageUid = {$plugin.tx_eptheme.settings.skipassPageUid} + englishPageUid = {$plugin.tx_eptheme.settings.englishPageUid} + germanPageUid = {$plugin.tx_eptheme.settings.germanPageUid} + conceptsPageUid = {$plugin.tx_eptheme.settings.conceptsPageUid} + globalConceptCode = {$plugin.tx_eptheme.settings.globalConceptCode} + travelAlertPageUid = {$plugin.tx_eptheme.settings.travelAlertPageUid} + myEpPageUid = {$plugin.tx_eptheme.settings.myEpPageUid} + myEpApiEndpointUrl = {$plugin.tx_eptheme.settings.myEpApiEndpointUrl} + myEpApiToken = {$plugin.tx_eptheme.settings.myEpApiToken} + myEpApiTimeout = {$plugin.tx_eptheme.settings.myEpApiTimeout} + facebookViewContentValues { + productUid = {$plugin.tx_eptheme.settings.facebookViewContentProductUid} + nameInternal = {$plugin.tx_eptheme.settings.facebookViewContentNameInternal} + } + domainsToTrack = bpn.ep-reisen.de,www.ski-boarderweek.de,www.ski-wochenenden.de,www.snowzone.net,www.unichamp.de + } + view { + templateRootPaths.0 = EXT:ep_theme/Resources/Private/Templates + partialRootPaths.0 = EXT:ep_theme/Resources/Private/Partials + layoutRootPaths.0 = EXT:ep_theme/Resources/Private/Layouts + } +} + +# Enable section index menus on invisible pages +tt_content.menu_section.dataProcessing.10.includeNotInMenu = 1 + +@import 'EXT:ep_theme/Configuration/TypoScript/Page/*' + +config { + compressJs = 1 + compressCss = 1 + typolinkCheckRootline = 1 + typolinkEnableLinksAcrossDomains = 1 + sendCacheHeaders = 1 + content_from_pid_allowOutsideDomain = 1 + inlineStyle2TempFile = 0 + additionalHeaders { +# 10 { +# header = Content-Security-Policy: default-src 'self' 'unsafe-inline'; connect-src 'self' https://my.ep-reisen.de/; +# } + 20 { + header = Strict-Transport-Security: max-age=31536000; includeSubDomains; preload + } + 30 { + header = X-Frame-Options: sameorigin + } + 40 { + header = Referrer-Policy: same-origin + } + 50 { + header = Permissions-Policy: microphone=(), camera=() + } + } +} diff --git a/packages/ep_theme/Configuration/Yaml/BaseSetup.yaml b/packages/ep_theme/Configuration/Yaml/BaseSetup.yaml new file mode 100644 index 0000000..79da447 --- /dev/null +++ b/packages/ep_theme/Configuration/Yaml/BaseSetup.yaml @@ -0,0 +1,39 @@ +TYPO3: + CMS: + Form: + persistenceManager: + allowedExtensionPaths: + 10: EXT:ep_theme/Resources/Private/Forms/ + + prototypes: + standard: + + formElementsDefinition: + Form: + renderingOptions: + templateRootPaths: + 1505042810: 'EXT:ep_theme/Resources/Private/Templates/Form/' + partialRootPaths: + 1623227657: 'EXT:ep_theme/Resources/Private/Partials/Form/' + layoutRootPaths: + 1505042810: 'EXT:ep_theme/Resources/Private/Layouts/Form/' + honeypot: + enable: false + + finishersDefinition: + BpnApiFinisher: + implementationClassName: 'EP\EpTheme\Form\BpnApiFinisher' + ApplicationApiFinisher: + implementationClassName: 'EP\EpTheme\Form\ApplicationApiFinisher' + WebhookFinisher: + implementationClassName: 'EP\EpTheme\Form\WebhookFinisher' + AddReferrerFinisher: + implementationClassName: 'EP\EpTheme\Form\AddReferrerFinisher' + + contactform: + __inheritances: + 10: 'TYPO3.CMS.Form.prototypes.standard' + + applicationform: + __inheritances: + 10: 'TYPO3.CMS.Form.prototypes.standard' diff --git a/packages/ep_theme/Configuration/Yaml/FormEditorSetup.yaml b/packages/ep_theme/Configuration/Yaml/FormEditorSetup.yaml new file mode 100644 index 0000000..e69de29 diff --git a/packages/ep_theme/Configuration/Yaml/FormEngineSetup.yaml b/packages/ep_theme/Configuration/Yaml/FormEngineSetup.yaml new file mode 100644 index 0000000..e69de29 diff --git a/packages/ep_theme/Configuration/Yaml/FormFinishersBackend.yaml b/packages/ep_theme/Configuration/Yaml/FormFinishersBackend.yaml new file mode 100644 index 0000000..25d8745 --- /dev/null +++ b/packages/ep_theme/Configuration/Yaml/FormFinishersBackend.yaml @@ -0,0 +1,111 @@ +TYPO3: + CMS: + Form: + prototypes: + standard: + formElementsDefinition: + Form: + formEditor: + editors: + 900: + selectOptions: + 1000: + value: 'BpnApiFinisher' + label: 'BusPro Finisher' + 1010: + value: 'ApplicationApiFinisher' + label: 'Application Finisher' + 1050: + value: 'WebhookFinisher' + label: 'Webhook Finisher' + 1100: + value: 'AddReferrerFinisher' + label: 'Referrer Finisher' + propertyCollections: + finishers: + 1000: + identifier: 'BpnApiFinisher' + editors: + __inheritances: + 10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin' + 100: + label: 'BusPro Finisher' + 110: + identifier: 'mapping' + templateName: 'Inspector-TextEditor' + label: 'Mapping' + propertyPath: 'options.mapping' + propertyValidators: + 10: 'NotEmpty' + 1010: + identifier: 'ApplicationApiFinisher' + editors: + __inheritances: + 10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin' + 100: + label: 'Application Finisher' + 110: + identifier: 'mapping' + templateName: 'Inspector-TextEditor' + label: 'Mapping' + propertyPath: 'options.mapping' + propertyValidators: + 10: 'NotEmpty' + 120: + identifier: 'exclude' + templateName: 'Inspector-TextEditor' + label: 'Exclude' + propertyPath: 'options.exclude' + 1050: + identifier: 'WebhookFinisher' + editors: + __inheritances: + 10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin' + 100: + label: 'Webhook Finisher' + 110: + identifier: 'mapping' + templateName: 'Inspector-TextEditor' + label: 'Mapping' + propertyPath: 'options.mapping' + propertyValidators: + 10: 'NotEmpty' + 120: + identifier: 'url' + templateName: 'Inspector-TextEditor' + label: 'URL' + propertyPath: 'options.url' + propertyValidators: + 10: 'NotEmpty' + 1100: + identifier: 'AddReferrerFinisher' + editors: + __inheritances: + 10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin' + 100: + label: 'Referrer Finisher' + + finishersDefinition: + BpnApiFinisher: + formEditor: + iconIdentifier: 'form-finisher' + predefinedDefaults: + options: + mapping: 'lastName:text-1,firstName:text-2,gender:singleselect-1,email:email-1,phone:telephone-1' + ApplicationApiFinisher: + formEditor: + iconIdentifier: 'form-finisher' + predefinedDefaults: + options: + mapping: 'firstName:text-1,lastName:text-2,email:email-1,phone:telephone-1' + exclude: 'captcha,privacypolicyaccepted' + WebhookFinisher: + formEditor: + iconIdentifier: 'form-finisher' + predefinedDefaults: + options: + mapping: 'lastName:text-1,firstName:text-2,gender:singleselect-1,email:email-1,phone:telephone-1' + url: '' + AddReferrerFinisher: + formEditor: + iconIdentifier: 'form-finisher' diff --git a/packages/ep_theme/Configuration/Yaml/FormFinishersFrontend.yaml b/packages/ep_theme/Configuration/Yaml/FormFinishersFrontend.yaml new file mode 100644 index 0000000..fbd9d99 --- /dev/null +++ b/packages/ep_theme/Configuration/Yaml/FormFinishersFrontend.yaml @@ -0,0 +1,35 @@ +TYPO3: + CMS: + Form: + prototypes: + standard: + finishersDefinition: + BpnApiFinisher: + implementationClassName: 'EP\EpTheme\Form\BpnApiFinisher' + formEditor: + label: 'BusPro Finisher' + options: + identifier: 'BpnApiFinisher' + mapping: 'lastName:text-1,firstName:text-2,gender:singleselect-1,email:email-1,phone:telephone-1' + ApplicationApiFinisher: + implementationClassName: 'EP\EpTheme\Form\ApplicationApiFinisher' + formEditor: + label: 'Application Finisher' + options: + identifier: 'ApplicationApiFinisher' + mapping: 'firstName:text-1,lastName:text-2,email:email-1,phone:telephone-1' + exclude: 'captcha,privacypolicyaccepted' + WebhookFinisher: + implementationClassName: 'EP\EpTheme\Form\WebhookFinisher' + formEditor: + label: 'Webhook Finisher' + options: + identifier: 'WebhookFinisher' + mapping: 'lastName:text-1,firstName:text-2,gender:singleselect-1,email:email-1,phone:telephone-1' + url: '' + AddReferrerFinisher: + implementationClassName: 'EP\EpTheme\Form\AddReferrerFinisher' + formEditor: + label: 'Referrer Finisher' + options: + identifier: 'AddReferrerFinisher' diff --git a/packages/ep_theme/Configuration/Yaml/FormsBase.yaml b/packages/ep_theme/Configuration/Yaml/FormsBase.yaml new file mode 100644 index 0000000..f7d0c3f --- /dev/null +++ b/packages/ep_theme/Configuration/Yaml/FormsBase.yaml @@ -0,0 +1,42 @@ +TYPO3: + CMS: + Form: + persistenceManager: + allowedExtensionPaths: + 10: EXT:ep_theme/Resources/Private/Forms/ + prototypes: + standard: + formEditor: + formElementGroups: + input: + label: Basiselemente + html5: + label: HTML5-Elemente + select: + label: Auswahlelemente + custom: + label: Sonstiges + formElementsDefinition: + AdvancedPassword: null + DatePicker: null + Fieldset: null + GridRow: null + Hidden: null + ImageUpload: null + Password: null + Form: + renderingOptions: + templateRootPaths: + 1505042810: 'EXT:ep_theme/Resources/Private/Templates/Form/' + partialRootPaths: + 1623227657: 'EXT:ep_theme/Resources/Private/Partials/Form/' + layoutRootPaths: + 1505042810: 'EXT:ep_theme/Resources/Private/Layouts/Form/' + honeypot: + enable: false + contactform: + __inheritances: + 10: 'TYPO3.CMS.Form.prototypes.standard' + applicationform: + __inheritances: + 10: 'TYPO3.CMS.Form.prototypes.standard' diff --git a/packages/ep_theme/Resources/Private/.htaccess b/packages/ep_theme/Resources/Private/.htaccess new file mode 100755 index 0000000..3418e55 --- /dev/null +++ b/packages/ep_theme/Resources/Private/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff new file mode 100644 index 0000000..a18ddb1 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff differ diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff2 b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff2 new file mode 100644 index 0000000..a7f756c Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff2 differ diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-700.woff b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-700.woff new file mode 100644 index 0000000..abf0196 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-700.woff differ diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-700.woff2 b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-700.woff2 new file mode 100644 index 0000000..f8c37ec Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-700.woff2 differ diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-900.woff b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-900.woff new file mode 100644 index 0000000..6f201e0 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-900.woff differ diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-900.woff2 b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-900.woff2 new file mode 100644 index 0000000..2f647be Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-900.woff2 differ diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-regular.woff b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-regular.woff new file mode 100644 index 0000000..189a0fe Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-regular.woff differ diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-regular.woff2 b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-regular.woff2 new file mode 100644 index 0000000..6904b66 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-regular.woff2 differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/ajax-loader-blue.gif b/packages/ep_theme/Resources/Private/Assets/images/ajax-loader-blue.gif new file mode 100644 index 0000000..748d53f Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/ajax-loader-blue.gif differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/ajax-loader-white.gif b/packages/ep_theme/Resources/Private/Assets/images/ajax-loader-white.gif new file mode 100644 index 0000000..a416354 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/ajax-loader-white.gif differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/at.png b/packages/ep_theme/Resources/Private/Assets/images/at.png new file mode 100755 index 0000000..3ad010b Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/at.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/background/header-sommer.jpg b/packages/ep_theme/Resources/Private/Assets/images/background/header-sommer.jpg new file mode 100644 index 0000000..5dede6c Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/background/header-sommer.jpg differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/background/header-winter.jpg b/packages/ep_theme/Resources/Private/Assets/images/background/header-winter.jpg new file mode 100644 index 0000000..5a46d2d Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/background/header-winter.jpg differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/background/pattern.jpg b/packages/ep_theme/Resources/Private/Assets/images/background/pattern.jpg new file mode 100644 index 0000000..dcf82ce Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/background/pattern.jpg differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/background/winter.jpg b/packages/ep_theme/Resources/Private/Assets/images/background/winter.jpg new file mode 100644 index 0000000..1a96b54 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/background/winter.jpg differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/badge_skipass.png b/packages/ep_theme/Resources/Private/Assets/images/badge_skipass.png new file mode 100644 index 0000000..8f0d328 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/badge_skipass.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/badge_skipass_black.png b/packages/ep_theme/Resources/Private/Assets/images/badge_skipass_black.png new file mode 100644 index 0000000..1196b5c Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/badge_skipass_black.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/bg_login.jpg b/packages/ep_theme/Resources/Private/Assets/images/bg_login.jpg new file mode 100644 index 0000000..10588da Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/bg_login.jpg differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/ch.png b/packages/ep_theme/Resources/Private/Assets/images/ch.png new file mode 100755 index 0000000..97c435c Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/ch.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_1.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_1.svg new file mode 100644 index 0000000..2050381 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_1.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_3.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_3.svg new file mode 100644 index 0000000..ae12231 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_3.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_4.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_4.svg new file mode 100644 index 0000000..219af6f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_4.svg @@ -0,0 +1,12 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_deal.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_deal.svg new file mode 100644 index 0000000..af8fc30 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_deal.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_1.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_1.svg new file mode 100644 index 0000000..1dd724d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_1.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_2.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_2.svg new file mode 100644 index 0000000..6d70447 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_2.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_3.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_3.svg new file mode 100644 index 0000000..0f00471 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_3.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_4.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_4.svg new file mode 100644 index 0000000..1cb85ba --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_4.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_5.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_5.svg new file mode 100644 index 0000000..7d8a48b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_5.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_deal.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_deal.svg new file mode 100644 index 0000000..c8215ac --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_deal.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_hilfe.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_hilfe.svg new file mode 100644 index 0000000..94aca0f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_hilfe.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/de.png b/packages/ep_theme/Resources/Private/Assets/images/de.png new file mode 100644 index 0000000..4b06ae2 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/de.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/favicon_ep.ico b/packages/ep_theme/Resources/Private/Assets/images/favicon_ep.ico new file mode 100644 index 0000000..51a2626 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/favicon_ep.ico differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/favicon_sbw.ico b/packages/ep_theme/Resources/Private/Assets/images/favicon_sbw.ico new file mode 100644 index 0000000..e8b01b6 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/favicon_sbw.ico differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/favicon_ser.ico b/packages/ep_theme/Resources/Private/Assets/images/favicon_ser.ico new file mode 100644 index 0000000..6e5d52d Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/favicon_ser.ico differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/favicon_sz.ico b/packages/ep_theme/Resources/Private/Assets/images/favicon_sz.ico new file mode 100644 index 0000000..c1ace1d Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/favicon_sz.ico differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/favicon_uch.ico b/packages/ep_theme/Resources/Private/Assets/images/favicon_uch.ico new file mode 100644 index 0000000..96a30c8 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/favicon_uch.ico differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/flags/at.svg b/packages/ep_theme/Resources/Private/Assets/images/flags/at.svg new file mode 100644 index 0000000..e63fb2e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/flags/at.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/flags/ch.svg b/packages/ep_theme/Resources/Private/Assets/images/flags/ch.svg new file mode 100644 index 0000000..f5ec8d4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/flags/ch.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/flags/de.svg b/packages/ep_theme/Resources/Private/Assets/images/flags/de.svg new file mode 100644 index 0000000..1acf302 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/flags/de.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/flags/fr.svg b/packages/ep_theme/Resources/Private/Assets/images/flags/fr.svg new file mode 100644 index 0000000..712c8a5 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/flags/fr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/flags/gb.svg b/packages/ep_theme/Resources/Private/Assets/images/flags/gb.svg new file mode 100644 index 0000000..132dbed --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/flags/gb.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/fr.png b/packages/ep_theme/Resources/Private/Assets/images/fr.png new file mode 100755 index 0000000..884bb02 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/fr.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/gb.png b/packages/ep_theme/Resources/Private/Assets/images/gb.png new file mode 100644 index 0000000..aedceaf Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/gb.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/gb_grey.png b/packages/ep_theme/Resources/Private/Assets/images/gb_grey.png new file mode 100644 index 0000000..8bc7b3c Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/gb_grey.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/global_compact.png b/packages/ep_theme/Resources/Private/Assets/images/global_compact.png new file mode 100644 index 0000000..f50c779 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/global_compact.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/global_compact_weiss.png b/packages/ep_theme/Resources/Private/Assets/images/global_compact_weiss.png new file mode 100644 index 0000000..152432c Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/global_compact_weiss.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-114x114.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-114x114.png new file mode 100644 index 0000000..456dedc Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-114x114.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-120x120.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-120x120.png new file mode 100644 index 0000000..0340efa Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-120x120.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-128x128.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-128x128.png new file mode 100644 index 0000000..3ecb2f6 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-128x128.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-144x144.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-144x144.png new file mode 100644 index 0000000..11ce010 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-144x144.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-152x152.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-152x152.png new file mode 100644 index 0000000..4ec6242 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-152x152.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-180x180.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-180x180.png new file mode 100644 index 0000000..45abefb Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-180x180.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-57x57.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-57x57.png new file mode 100644 index 0000000..ad2c3a7 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-57x57.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-60x60.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-60x60.png new file mode 100644 index 0000000..c07c9ba Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-60x60.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-72x72.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-72x72.png new file mode 100644 index 0000000..b065b75 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-72x72.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-76x76.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-76x76.png new file mode 100644 index 0000000..3f32e54 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-76x76.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-precomposed.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-precomposed.png new file mode 100644 index 0000000..ad2c3a7 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-precomposed.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon.png new file mode 100644 index 0000000..c07c9ba Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-160x160.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-160x160.png new file mode 100644 index 0000000..60bd68f Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-160x160.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-16x16.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-16x16.png new file mode 100644 index 0000000..ee7fb61 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-16x16.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-192x192.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-192x192.png new file mode 100644 index 0000000..962223c Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-192x192.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-196x196.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-196x196.png new file mode 100644 index 0000000..d456d0d Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-196x196.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-32x32.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-32x32.png new file mode 100644 index 0000000..994f5b3 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-32x32.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-96x96.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-96x96.png new file mode 100644 index 0000000..d800b72 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-96x96.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.gif b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.gif new file mode 100644 index 0000000..73a65f8 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.gif differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.ico b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.ico new file mode 100644 index 0000000..3483434 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.ico differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.png new file mode 100644 index 0000000..34c4504 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/icon_ce.svg b/packages/ep_theme/Resources/Private/Assets/images/icons/icon_ce.svg new file mode 100644 index 0000000..e23d1f8 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/icons/icon_ce.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/icon_select.png b/packages/ep_theme/Resources/Private/Assets/images/icons/icon_select.png new file mode 100644 index 0000000..bbcbc23 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/icon_select.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_lineup.gif b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_lineup.gif new file mode 100644 index 0000000..e6c57ec Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_lineup.gif differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_slide.gif b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_slide.gif new file mode 100644 index 0000000..e6c57ec Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_slide.gif differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_half_and_two_quarters.png b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_half_and_two_quarters.png new file mode 100644 index 0000000..a29ebee Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_half_and_two_quarters.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_halves.png b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_halves.png new file mode 100644 index 0000000..bfdaf3a Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_halves.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_quarters.png b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_quarters.png new file mode 100644 index 0000000..037522a Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_quarters.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_thirds.png b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_thirds.png new file mode 100644 index 0000000..f83afd3 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_thirds.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_two_quarters_and_half.png b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_two_quarters_and_half.png new file mode 100644 index 0000000..1cf69bc Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_two_quarters_and_half.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons_sprite.svg b/packages/ep_theme/Resources/Private/Assets/images/icons_sprite.svg new file mode 100644 index 0000000..1e817d6 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/icons_sprite.svg @@ -0,0 +1,85 @@ + diff --git a/packages/ep_theme/Resources/Private/Assets/images/lazy.png b/packages/ep_theme/Resources/Private/Assets/images/lazy.png new file mode 100644 index 0000000..a84a4a8 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/lazy.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo.svg b/packages/ep_theme/Resources/Private/Assets/images/logo.svg new file mode 100644 index 0000000..f3753f4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_boarderweek.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_boarderweek.svg new file mode 100644 index 0000000..64d3561 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_boarderweek.svg @@ -0,0 +1,673 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_ser.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_ser.svg new file mode 100644 index 0000000..05ab197 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_ser.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone.svg new file mode 100644 index 0000000..c693f6f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone.svg @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone_2019.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone_2019.svg new file mode 100644 index 0000000..7b534d9 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone_2019.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_uni_champ.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_uni_champ.svg new file mode 100644 index 0000000..e3c02fb --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_uni_champ.svg @@ -0,0 +1,1951 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_wide.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_wide.svg new file mode 100644 index 0000000..596f534 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_wide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ep_theme/Resources/Private/Assets/images/low-poly.jpg b/packages/ep_theme/Resources/Private/Assets/images/low-poly.jpg new file mode 100644 index 0000000..dcf82ce Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/low-poly.jpg differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/polygon.jpg b/packages/ep_theme/Resources/Private/Assets/images/polygon.jpg new file mode 100644 index 0000000..09f1738 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/polygon.jpg differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/pow_de.png b/packages/ep_theme/Resources/Private/Assets/images/pow_de.png new file mode 100644 index 0000000..990709b Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/pow_de.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/images/signature_ep_team.png b/packages/ep_theme/Resources/Private/Assets/images/signature_ep_team.png new file mode 100644 index 0000000..4d0cca5 Binary files /dev/null and b/packages/ep_theme/Resources/Private/Assets/images/signature_ep_team.png differ diff --git a/packages/ep_theme/Resources/Private/Assets/js/_app.js b/packages/ep_theme/Resources/Private/Assets/js/_app.js new file mode 100644 index 0000000..8802344 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/_app.js @@ -0,0 +1,46 @@ +// Polyfills +// import smoothscroll from 'smoothscroll-polyfill' +// smoothscroll.polyfill() + +// Add pacode to external links +document.addEventListener('DOMContentLoaded', () => { + const configElement = document.getElementById('appconfig') + if (null === configElement) { + return {} + } + const appConfig = JSON.parse(configElement.innerHTML) + const paCode = appConfig.paCode + const externalDomains = appConfig.externalDomains + if (paCode) { + document.querySelectorAll('a').forEach(element => { + if (element.hostname === location.hostname) { + return + } + if (externalDomains.indexOf(element.hostname) === -1) { + return + } + let url = element.href + if (!url) { + return + } + if (url.indexOf('agenturcode=') !== -1) { + return + } + const separator = url.indexOf('?') === -1 ? '?' : '&' + url += separator + 'agenturcode=' + paCode + element.href = url + }) + } +}, false) + +import { Application } from '@hotwired/stimulus' +import { definitionsFromContext } from '@hotwired/stimulus-webpack-helpers' + +const application = Application.start() +const context = require.context('./controllers', true, /\.js$/) +application.load(definitionsFromContext(context)) + +window.htmx = require('htmx.org') +htmx.config.includeIndicatorStyles = false +htmx.config.historyEnabled = false +htmx.config.allowScriptTags = false diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/autocollapse_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/autocollapse_controller.js new file mode 100644 index 0000000..a43c527 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/autocollapse_controller.js @@ -0,0 +1,54 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = [ 'sizer', 'container', 'button', 'bar' ] + + static values = { + offset: { + type: Number, + default: 0, + }, + height: { + type: Number, + default: 0, + }, + expanded: { + type: Boolean, + default: true, + } + } + + connect() { + // Determine height of element controlling max height if available + this.maxHeight = this.hasSizerTarget ? this.sizerTarget.offsetHeight : this.heightValue + + // Determine rendered height of element holding content + this.contentHeight = this.containerTarget.offsetHeight + + // Determine height of collapsed content element considering + // potential offset for button + this.collapsedHeight = this.maxHeight - this.offsetValue + + // Set initial expanded/collapsed status and button visibility + this.expandedValue = this.contentHeight < this.maxHeight + this.buttonTarget.classList.toggle('hidden', this.expandedValue) + + // Drop the space reserved for the toggle bar when the content fits anyway + if (this.expandedValue && this.hasBarTarget) { + this.barTarget.classList.add('hidden') + this.containerTarget.style.paddingBottom = '0' + } + } + + toggle() { + this.expandedValue = !this.expandedValue + } + + expandedValueChanged(expanded) { + this.containerTarget.style.height = expanded ? 'auto' : `${this.collapsedHeight}px` + this.containerTarget.style.overflowY = expanded ? 'none' : 'hidden' + this.buttonTarget.classList.toggle('toggle-more', false === expanded) + this.buttonTarget.classList.toggle('toggle-less', expanded) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/calendar_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/calendar_controller.js new file mode 100644 index 0000000..1e34b8e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/calendar_controller.js @@ -0,0 +1,68 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch } from '../mixins/use_fetch' + +export default class extends Controller { + + static targets = [ + 'container', + 'field', + 'loadingIndicator', + ] + + static values = { + loading: Boolean, + uri: String, + hotelUid: Number, + } + + initialize() { + useFetch(this) + } + + connect() { + let months = [] + let today = new Date() + let year = today.getFullYear() + let month = today.getMonth() + for (let i = 0; i < 20; i++) { + let selectableDate = new Date(year, month, 1) + months.push(selectableDate) + if (month < 12) { + month++ + } else { + month = 1 + year++ + } + this.fieldTarget.options[i] = new Option(selectableDate.toLocaleDateString('de-DE', {year: 'numeric', month: 'long'}), i.toString()) + } + this.selectableMonths = months + this.load(0) + } + + select(e) { + let selectedIndex = e.currentTarget.value + this.load(selectedIndex) + } + + load(selectedIndex) { + this.loadingValue = true + let selectedMonth = this.selectableMonths[selectedIndex] + let data = new URLSearchParams() + data.set('tx_epproducts_ajax[hotel]', this.hotelUidValue) + data.set('tx_epproducts_ajax[month]', selectedMonth.getMonth() + 1) + data.set('tx_epproducts_ajax[year]', selectedMonth.getFullYear()) + fetch(this.uriValue, { method: 'POST', body: data }) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => ( + this.containerTarget.innerHTML = html + )) + .finally(() => { + this.loadingValue = false + }) + } + + loadingValueChanged() { + this.loadingIndicatorTarget.classList.toggle('hidden', this.loadingValue === false) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/collapse_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/collapse_controller.js new file mode 100644 index 0000000..404a28d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/collapse_controller.js @@ -0,0 +1,58 @@ +import { Controller } from '@hotwired/stimulus' +import { useWindowResize, useDebounce, useClickOutside } from 'stimulus-use' + +export default class extends Controller { + + static debounces = [ 'checkBreakpoints' ] + + static values = { + collapsed: Boolean, + from: Number, + until: Number, + } + + static targets = [ 'container', 'icon' ] + + initialize() { + useWindowResize(this) + useDebounce(this) + useClickOutside(this) + } + + connect() { + this.toggleByWindowWidth(window.innerWidth) + } + + windowResize({ width, height, event }) { + this.toggleByWindowWidth(width) + } + + toggle() { + this.collapsedValue = !this.collapsedValue + } + + open() { + this.collapsedValue = false + } + + close() { + this.collapsedValue = true + } + + toggleByWindowWidth(width) { + if (0 !== this.untilValue) { + this.collapsedValue = width < this.untilValue + } + if (0 !== this.fromValue) { + this.collapsedValue = width > this.fromValue + } + } + + collapsedValueChanged() { + this.containerTarget.classList.toggle('hidden', this.collapsedValue === true) + this.containerTarget.scrollTop = 0 + if (this.hasIconTarget) { + this.iconTarget.classList.toggle('rotate-90', this.collapsedValue === false) + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/datalayer_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/datalayer_controller.js new file mode 100644 index 0000000..f71b5f8 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/datalayer_controller.js @@ -0,0 +1,14 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static values = { event: String } + + trigger() { + window.dataLayer = window.dataLayer || [] + dataLayer.push({ + 'event': this.eventValue, + }) + } + +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/datepicker_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/datepicker_controller.js new file mode 100644 index 0000000..c011c48 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/datepicker_controller.js @@ -0,0 +1,109 @@ +import { Controller } from '@hotwired/stimulus' +import { useDispatch } from 'stimulus-use' +import flatpickr from 'flatpickr' +import { German } from 'flatpickr/dist/l10n/de' + +export default class extends Controller { + + static targets = [ + 'field', + 'dateFrom', + 'dateTo', + ] + + static values = { + inline: Boolean, + minDate: String, + dateFrom: String, + dateTo: String, + enabledDates: Array, + } + + initialize() { + useDispatch(this) + const inline = this.hasInlineValue ? this.inlineValue : true + this.picker = flatpickr(this.fieldTarget, { + mode: 'range', + locale: German, + inline, + minDate: this.minDateValue, + altFormat: 'd.m.Y', + altInput: !inline, + onChange: selectedDates => { + if (selectedDates.length === 2) { + this.dateFromValue = flatpickr.formatDate(selectedDates[0], 'Y-m-d') + this.dateToValue = flatpickr.formatDate(selectedDates[1], 'Y-m-d') + this.update() + } + } + }) + if (this.hasEnabledDatesValue) { + let enabledDates = this.enabledDatesValue + let minDate = enabledDates[0] + let maxDate = enabledDates[enabledDates.length - 1] + if (this.hasMinDateValue && this.minDateValue > minDate) { + minDate = this.minDateValue + } + this.picker.set('enable', enabledDates) + this.picker.set('minDate', minDate) + this.picker.set('maxDate', maxDate) + this.picker.jumpToDate(minDate) + this.picker.set('onDayCreate', (dObj, dStr, fp, dayElem) => { + let day = flatpickr.formatDate(dayElem.dateObj, 'Y-m-d') + if (day >= minDate && -1 !== this.enabledDatesValue.indexOf(day)) { + dayElem.classList.add('available') + } + }) + } + } + + connect() { + if ('' !== this.dateFromValue && '' !== this.dateToValue) { + let dateFrom = new Date(this.dateFromValue) + let dateTo = new Date(this.dateToValue) + this.picker.setDate([ dateFrom, dateTo ]) + } + } + + selectPreset(e) { + e.preventDefault() + const preset = JSON.parse(e.currentTarget.dataset.preset) + this.dateFromValue = preset.dateFrom + this.dateToValue = preset.dateTo + let dateFrom = new Date(preset.dateFrom) + let dateTo = new Date(preset.dateTo) + this.picker.setDate([ dateFrom, dateTo ]) + this.update() + } + + reset(e) { + e.preventDefault() + this.picker.clear() + this.picker.jumpToDate() + this.dateFromValue = '' + this.dateToValue = '' + this.update() + } + + update() { + // Set values to form fields if applicable + if (this.hasDateFromTarget) { + this.dateFromTarget.value = this.dateFromValue + } + if (this.hasDateToTarget) { + this.dateToTarget.value = this.dateToValue + } + // Dispatch event with selected values and formatted range label + let label = '' + if (this.dateFromValue && this.dateToValue) { + let dateFrom = new Date(this.dateFromValue) + let dateTo = new Date(this.dateToValue) + label = `${flatpickr.formatDate(dateFrom, 'd.m.Y')}-${flatpickr.formatDate(dateTo, 'd.m.Y')}` + } + this.dispatch('range-select', { + from: this.dateFromValue, + to: this.dateToValue, + label + }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/daytrip_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/daytrip_controller.js new file mode 100644 index 0000000..092a3d8 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/daytrip_controller.js @@ -0,0 +1,62 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch } from '../mixins/use_fetch' + +export default class extends Controller { + + static targets = [ + 'container', + 'loadingIndicator', + 'message', + ] + + static values = { + loading: Boolean, + contingentsUri: String, + roomsUri: String, + hotelUid: Number, + productUid: Number, + dateFrom: String, + dateTo: String, + } + + initialize() { + useFetch(this) + } + + openModal({ params }) { + let eventName = `${params.type}-modal:open` + this.dispatch(eventName, { + detail: { + type: params.type, + uid: params.uid + } + }) + } + + load(e) { + this.loadingValue = true + this.dateFromValue = e.detail.from + this.dateToValue = e.detail.to + this.containerTarget.innerHTML = '' + let data = new FormData() + data.set('tx_epproducts_ajax[hotel]', this.hotelUidValue) + data.set('tx_epproducts_ajax[product]', this.productUidValue) + data.set('tx_epproducts_ajax[dateFrom]', this.dateFromValue) + data.set('tx_epproducts_ajax[dateTo]', this.dateToValue) + fetch(this.roomsUriValue, { method: 'POST', body: data }) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.containerTarget.innerHTML = html + this.messageTarget.classList.add('hidden') + }) + .finally(() => { + this.loadingValue = false + }) + } + + loadingValueChanged(isLoading) { + this.loadingIndicatorTarget.classList.toggle('hidden', false === isLoading) + this.loadingIndicatorTarget.classList.toggle('flex', true === isLoading) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/faq_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/faq_controller.js new file mode 100644 index 0000000..874d280 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/faq_controller.js @@ -0,0 +1,42 @@ +import { Controller } from '@hotwired/stimulus' +import { useDebounce } from 'stimulus-use' + +export default class extends Controller { + + static debounces = [ 'filter' ] + + static targets = [ 'entry', 'category', 'empty' ] + + initialize() { + useDebounce(this) + } + + filter(e) { + let value = e.target.value + if (value.length < 3) { + this.entryTargets.forEach(entry => entry.classList.remove('hidden')) + this.categoryTargets.forEach(category => category.classList.remove('hidden')) + this.toggleEmpty(false) + return + } + const pattern = new RegExp(value, 'i') + this.entryTargets.forEach(entry => { + const question = entry.querySelector('button span').textContent + const answer = entry.querySelector('[data-rte-content]').textContent + entry.classList.toggle('hidden', !question.match(pattern) && !answer.match(pattern)) + }) + this.categoryTargets.forEach(category => { + const hasVisible = Array.from(category.querySelectorAll('[data-faq-target="entry"]')) + .some(entry => !entry.classList.contains('hidden')) + category.classList.toggle('hidden', !hasVisible) + }) + const hasVisible = this.entryTargets.some(entry => !entry.classList.contains('hidden')) + this.toggleEmpty(!hasVisible) + } + + toggleEmpty(show) { + if (this.hasEmptyTarget) { + this.emptyTarget.classList.toggle('hidden', !show) + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/form_debounce_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/form_debounce_controller.js new file mode 100644 index 0000000..21474f2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/form_debounce_controller.js @@ -0,0 +1,25 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = ['button'] + static classes = [ 'disabled' ] + static values = { + isSubmitting: Boolean, + submittingLabel: { type: String, default: 'wird versendet' } + } + + debounce(e) { + if (true === this.isSubmittingValue) { + e.preventDefault() + e.stopPropagation() + return false + } + this.isSubmittingValue = true + this.buttonTarget.innerText = this.submittingLabelValue + if (this.hasDisabledClass) { + this.buttonTarget.classList.add(this.disabledClass) + } + } +} + diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/iframe_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/iframe_controller.js new file mode 100644 index 0000000..c6a592c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/iframe_controller.js @@ -0,0 +1,18 @@ +import { Controller } from '@hotwired/stimulus'; +import iframeResize from '@iframe-resizer/parent'; + +export default class extends Controller { + static values = { + license: { + type: String, + default: 'GPLv3', + } + } + + connect() { + iframeResize({ + license: this.licenseValue, + waitForLoad: true + }, this.element) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/lightbox_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/lightbox_controller.js new file mode 100644 index 0000000..780683c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/lightbox_controller.js @@ -0,0 +1,45 @@ +import { Controller } from '@hotwired/stimulus' +import Glightbox from 'glightbox' + +export default class extends Controller { + + static targets = [ 'thumb' ] + + static values = { url: String, height: String, type: String } + + connect() { + let elements = [] + let type = this.hasTypeValue ? this.typeValue : 'image' + if (this.hasUrlValue) { + let element = { href: this.urlValue, type } + if (this.hasHeightValue) { + element.height = this.heightValue + } + elements.push(element) + } else if (this.element.hasAttribute('href')) { + let element = { href: this.element.href, type } + elements.push(element) + } else { + this.thumbTargets.forEach(thumb => { + let element = { href: thumb.href, type } + let caption = this.element.hasAttribute('data-lightbox-caption') ? this.element.dataset.lightboxCaption : null + if (null !== caption) { + element.title = caption + } + elements.push(element) + }) + } + this.lightbox = Glightbox({ + elements + }) + } + + open(e) { + e.preventDefault() + let index = 1 + if (e.currentTarget.dataset.lightboxIndex) { + index = e.currentTarget.dataset.lightboxIndex + } + this.lightbox.openAt(index - 1) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js new file mode 100644 index 0000000..aea506f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js @@ -0,0 +1,80 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch} from '../mixins/use_fetch' + +export default class extends Controller { + + static targets = [ + 'form', + 'container', + 'loadingIndicator', + 'dateFrom', + 'dateTo', + ] + + static values = { + uri: String, + initUri: String, + loading: Boolean, + loaded: Boolean, + show: Boolean, + } + + initialize() { + useFetch(this) + } + + open(e) { + if (e.detail === 'mobile-search') { + this.showValue = true + if (false === this.loadedValue) { + this.load() + } + } + } + + close() { + this.showValue = false + } + + filter() { + this.load({ + method: 'POST', + body: new FormData(this.formTarget) + }) + } + + reset() { + this.load({ method: 'POST' }) + } + + load(options) { + let uri = this.loadedValue ? this.uriValue : this.initUriValue + this.loadingValue = true + fetch(uri, options) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.containerTarget.innerHTML = html + this.loadedValue = true + }) + .finally(() => { + this.loadingValue = false + }) + } + + selectDateRange(e) { + e.stopPropagation() + this.dateFromTarget.value = e.detail.from + this.dateToTarget.value = e.detail.to + this.filter() + } + + showValueChanged() { + this.element.classList.toggle('hidden', false === this.showValue) + } + + loadingValueChanged() { + this.containerTarget.classList.toggle('opacity-25', this.loadingValue === true) + this.loadingIndicatorTarget.classList.toggle('hidden', this.loadingValue === false) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/modal_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/modal_controller.js new file mode 100644 index 0000000..c3f2150 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/modal_controller.js @@ -0,0 +1,22 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = [ 'content' ] + + open({ detail: { uid } }) { + this.contentTargets.forEach(element => { + let elementUid = parseInt(element.dataset.uid) + element.classList.toggle('hidden', uid !== elementUid) + }) + this.element.classList.remove('hidden') + } + + close() { + this.element.classList.add('hidden', true) + } + + remove() { + this.element.remove() + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/nav_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/nav_controller.js new file mode 100644 index 0000000..e4bea2e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/nav_controller.js @@ -0,0 +1,36 @@ +import { Controller } from '@hotwired/stimulus' +import { useClickOutside } from 'stimulus-use' + +export default class extends Controller { + + static classes = ['hidden'] + static targets = ['button', 'subnav'] + static values = { expanded: Boolean } + + connect() { + useClickOutside(this) + } + + toggle(e) { + e.preventDefault() + this.expandedValue = !this.expandedValue + } + + clickOutside(e) { + this.expandedValue = false + } + + expandedValueChanged(expanded) { + if (this.hasButtonTarget) { + this.buttonTarget.setAttribute('aria-expanded', expanded) + } + if (this.hasSubnavTarget) { + this.subnavTarget.setAttribute('aria-hidden', !expanded) + if (true === expanded) { + this.subnavTarget.classList.remove(...this.hiddenClasses) + } else { + this.subnavTarget.classList.add(...this.hiddenClasses) + } + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/osm_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/osm_controller.js new file mode 100644 index 0000000..bb87d57 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/osm_controller.js @@ -0,0 +1,78 @@ +import { Controller } from '@hotwired/stimulus' +import L from 'leaflet' +import iconUrl from 'leaflet/dist/images/marker-icon.png' +import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png' +import shadowUrl from 'leaflet/dist/images/marker-shadow.png' + +// Icon.Default finds its images by reading the url() off .leaflet-default-icon-path and +// stripping a literal `marker-icon.png` off the end - which a content-hashed filename never +// matches, and the `link[href$="leaflet.css"]` fallback does not exist either because the +// CSS is inlined into the bundle. Dropping the _getIconUrl override falls back to plain +// option lookup, so the URLs Webpack emitted are used as they are. +delete L.Icon.Default.prototype._getIconUrl +L.Icon.Default.mergeOptions({ iconUrl, iconRetinaUrl, shadowUrl }) + +export default class extends Controller { + + static values = { + lat: Number, + lng: Number, + zoom: Number, + externalLink: String, + markers: Array, + tileUrl: String, + apiKey: String, + attribution: String, + } + + connect() { + this.map = L.map(this.element, { + scrollWheelZoom: false + }) + const zoom = this.hasZoomValue ? this.zoomValue : 12 + this.map.setZoom(zoom) + if (this.hasLatValue && this.hasLngValue) { + const position = L.latLng(this.latValue, this.lngValue) + const marker = L.marker(position) + if (this.hasExternalLinkValue && '' !== this.externalLinkValue) { + marker.on('click', () => { + window.open(this.externalLinkValue, '_blank') + }) + } else { + marker.on('click', () => { + window.open('https://www.google.com/maps/search/?api=1&zoom=15&query=' + this.latValue + ',' + this.lngValue, '_blank') + }) + } + marker.addTo(this.map) + this.map.setView(position) + } else if (this.hasMarkersValue && this.markersValue.length) { + const bounds = L.latLngBounds() + for (let item of this.markersValue) { + const position = L.latLng(item.lat, item.lng) + const marker = L.marker(position) + const popupContent = '
' + item.name + '
' + item.teaser + + '
zu den Details
' + marker.bindPopup(popupContent) + marker.addTo(this.map) + bounds.extend(position) + } + this.map.fitBounds(bounds) + } + L.tileLayer(this.tileUrl(), { + maxZoom: 19, + // The site sends Referrer-Policy: same-origin, which strips the Referer on + // cross-origin requests. The tile provider needs it to match its key against + // the allowed-domain list, so widen the policy for tile requests only. + referrerPolicy: 'strict-origin-when-cross-origin', + attribution: this.attributionValue + }).addTo(this.map) + } + + tileUrl() { + if ('' === this.tileUrlValue || '' === this.apiKeyValue) { + return '' + } + const separator = -1 === this.tileUrlValue.indexOf('?') ? '?' : '&' + return this.tileUrlValue + separator + 'apiKey=' + encodeURIComponent(this.apiKeyValue) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/overlay_toggle_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/overlay_toggle_controller.js new file mode 100644 index 0000000..641187f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/overlay_toggle_controller.js @@ -0,0 +1,32 @@ +import { Controller } from '@hotwired/stimulus' +import { useDispatch } from 'stimulus-use' + +export default class extends Controller { + + static values = { + id: String, + show: Boolean + } + + static targets = [ 'overlay' ] + + initialize() { + useDispatch(this) + } + + invokeToggle() { + this.dispatch('toggle', this.idValue) + } + + toggle(e) { + if (e.detail === this.idValue) { + this.showValue = !this.showValue + } + } + + showValueChanged() { + if (this.hasOverlayTarget) { + this.overlayTarget.classList.toggle('hidden', this.showValue === false) + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/price_calendar_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/price_calendar_controller.js new file mode 100644 index 0000000..a0ee14c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/price_calendar_controller.js @@ -0,0 +1,185 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = ['mount', 'error', 'message', 'button', 'resetButton', 'day', 'month', 'prevButton', 'nextButton'] + + static values = { + hotelCode: String, + groupsBookingUrl: String, + } + + connect() { + this.startDate = null + this.endDate = null + this.currentIndex = 0 + + // mountTarget carries hx-get, so HTMX fires its events there; listening here + // scopes to this calendar instance and avoids catching events from other HTMX elements. + this.mountTarget.addEventListener('htmx:afterSwap', () => { + this.showMonths(0) + this.showButton() + }) + this.mountTarget.addEventListener('htmx:responseError', () => this.showError()) + } + + showMonths(startIndex) { + this.currentIndex = startIndex + this.monthTargets.forEach((m, i) => { + m.classList.toggle('hidden', i < startIndex || i >= startIndex + 2) + }) + if (this.hasPrevButtonTarget) this.prevButtonTarget.disabled = startIndex === 0 + if (this.hasNextButtonTarget) this.nextButtonTarget.disabled = startIndex >= this.monthTargets.length - 2 + } + + prevMonths() { + this.showMonths(Math.max(0, this.currentIndex - 1)) + } + + nextMonths() { + this.showMonths(Math.min(this.currentIndex + 1, this.monthTargets.length - 2)) + } + + selectDate(event) { + const btn = event.currentTarget + // cooltipz shows on :focus as well as :hover; blurring dismisses it after a click. + btn.blur() + const date = btn.dataset.date + const status = btn.dataset.status + + // All day buttons carry data-action for template simplicity; non-selectable ones are filtered here. + if (!status || status === 'blocked') return + + if (this.startDate && this.endDate) { + this.resetSelection() + } + + if (!this.startDate) { + // checkout-only days allow departure but not arrival + if (status === 'checkout-only') { + this.showMessage('Bitte wähle einen Anreisetag.') + return + } + this.startDate = date + this.endDate = null + this.updateDisplay() + this.showMessage('Bitte wähle das Abreisedatum.') + this.disableButton() + return + } + + if (date === this.startDate) { + this.resetSelection() + return + } + + // Normalize so from < to regardless of which date the user clicked first. + const [from, to] = date < this.startDate ? [date, this.startDate] : [this.startDate, date] + + if (this.hasBlockedInRange(from, to)) { + this.resetSelection() + this.showMessage('Dieser Zeitraum enthält nicht verfügbare Daten.') + return + } + + const startBtn = this.dayTargets.find(b => b.dataset.date === from) + const minNights = parseInt(startBtn?.dataset.minNights) || 0 + const nights = (new Date(to) - new Date(from)) / 86400000 // ms → days + + // Short-stay warning is advisory only; the booking link is still enabled below. + if (minNights > 0 && nights < minNights) { + this.showMessage(`Weniger als ${minNights} Nächte nur auf Anfrage.`) + } else { + this.hideMessage() + } + + this.startDate = from + this.endDate = to + this.updateDisplay() + this.enableButton(from, to) + this.dispatch('range-select', { detail: { dateFrom: from, dateTo: to } }) + } + + hasBlockedInRange(from, to) { + for (const btn of this.dayTargets) { + const d = btn.dataset.date + // End date is exclusive: guests may check out of a blocked day, so it is not a hard blocker. + if (d >= from && d < to && btn.dataset.status === 'blocked') return true + } + return false + } + + updateDisplay() { + for (const btn of this.dayTargets) { + const d = btn.dataset.date + btn.classList.remove('pc-day--pending-start', 'pc-day--selected-start', 'pc-day--selected-end', 'pc-day--in-range') + if (!this.startDate) continue + if (d === this.startDate) { + btn.classList.add(this.endDate ? 'pc-day--selected-start' : 'pc-day--pending-start') + } else if (this.endDate) { + if (d === this.endDate) btn.classList.add('pc-day--selected-end') + else if (d > this.startDate && d < this.endDate) btn.classList.add('pc-day--in-range') + } + } + } + + resetSelection() { + this.startDate = null + this.endDate = null + this.updateDisplay() + this.hideMessage() + this.disableButton() + } + + reset() { + this.resetSelection() + } + + showButton() { + if (this.hasButtonTarget) this.buttonTarget.classList.remove('hidden') + } + + enableButton(dateFrom, dateTo) { + if (!this.hasButtonTarget) return + const params = new URLSearchParams({ + hotel_code: this.hotelCodeValue, + date_from: dateFrom, + date_to: dateTo, + }) + this.buttonTarget.href = `${this.groupsBookingUrlValue}?${params}` + this.buttonTarget.textContent = `${this.formatDate(dateFrom)}–${this.formatDate(dateTo)} Anfragen / Buchen` + this.buttonTarget.classList.remove('opacity-50', 'cursor-not-allowed', 'pointer-events-none') + if (this.hasResetButtonTarget) this.resetButtonTarget.classList.remove('hidden') + } + + disableButton() { + if (!this.hasButtonTarget) return + this.buttonTarget.removeAttribute('href') + this.buttonTarget.textContent = 'Anfragen / Buchen' + this.buttonTarget.classList.add('opacity-50', 'cursor-not-allowed', 'pointer-events-none') + if (this.hasResetButtonTarget) this.resetButtonTarget.classList.add('hidden') + } + + formatDate(iso) { + const [y, m, d] = iso.split('-') + return `${d}.${m}.${y.slice(2)}` + } + + showMessage(text) { + if (this.hasMessageTarget) { + this.messageTarget.textContent = text + } + } + + hideMessage() { + if (this.hasMessageTarget) { + this.messageTarget.textContent = '' + } + } + + showError() { + if (this.hasErrorTarget) { + this.errorTarget.classList.remove('hidden') + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/pricetable_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/pricetable_controller.js new file mode 100644 index 0000000..a05952d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/pricetable_controller.js @@ -0,0 +1,106 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = [ + 'row', + 'price', + 'busIcon', + 'surchargeToggleLabel', + 'discountToggleLabel', + 'rowToggleLabel', + 'rowToggleIcon', + ] + + static values = { + mode: String, + showAllRows: Boolean, + iconSpriteUrl: String, + } + + toggleSurcharge() { + this.modeValue = this.modeValue === 'surcharge' ? '' : 'surcharge' + } + + toggleDiscount() { + this.modeValue = this.modeValue === 'discount' ? '' : 'discount' + } + + toggleRows() { + this.showAllRowsValue = !this.showAllRowsValue + } + + openModal({ params }) { + let eventName = `${params.type}-modal:open` + this.dispatch(eventName, { + detail: { + type: params.type, + uid: params.uid + } + }) + } + + modeValueChanged() { + this.priceTargets.forEach(price => { + let room = parseFloat(price.dataset.room) + let bus = parseFloat(price.dataset.bus) + let discount = parseFloat(price.dataset.discount) + price.innerText = `${this.formatPrice(this.calculatePrice(room, bus, discount))} €` + }) + this.busIconTargets.forEach(icon => { + let busPrice = parseFloat(icon.dataset.busPrice) + let busIncluded = !!icon.dataset.busIncluded + icon.classList.toggle('hidden', this.isHideBusIcon(busPrice, busIncluded)) + }) + if (this.hasSurchargeToggleLabelTarget) { + let labelDefault = this.surchargeToggleLabelTarget.dataset.labelDefault + let labelAlt = this.surchargeToggleLabelTarget.dataset.labelAlt + this.surchargeToggleLabelTarget.innerText = this.modeValue === 'surcharge' ? labelDefault : labelAlt + } + if (this.hasDiscountToggleLabelTarget) { + let labelDefault = this.discountToggleLabelTarget.dataset.labelDefault + let labelAlt = this.discountToggleLabelTarget.dataset.labelAlt + this.discountToggleLabelTarget.innerText = this.modeValue === 'discount' ? labelDefault : labelAlt + } + } + + showAllRowsValueChanged() { + let additionalRows = this.rowTargets.slice(5) + if (additionalRows.length) { + additionalRows.forEach(row => { + row.classList.toggle('hidden', this.showAllRowsValue === false) + }) + } + if (this.hasRowToggleLabelTarget && this.hasRowToggleIconTarget) { + this.rowToggleLabelTarget.innerText = this.showAllRowsValue ? 'weniger Termine anzeigen' : 'alle Termine anzeigen' + this.rowToggleIconTarget.setAttribute('href', this.showAllRowsValue ? `${this.iconSpriteUrlValue}#icon-minus-circle` : `${this.iconSpriteUrlValue}#icon-plus-circle`) + } + } + + calculatePrice(minPrice, busPrice, discount) { + if ('surcharge' === this.modeValue) { + return minPrice + busPrice + } + if ('discount' === this.modeValue) { + return minPrice + discount + } + return minPrice + } + + // Keep in sync with EP\EpTheme\ViewHelpers\PriceViewHelper: whole euros + // as before ("229"), German decimals where there are cents ("99,90"). + formatPrice(amount) { + let cents = Math.round(amount * 100) + if (cents % 100 === 0) { + return String(cents / 100) + } + return (cents / 100).toFixed(2).replace('.', ',') + } + + isHideBusIcon(busPrice, busIncluded) { + return ( + busPrice && this.modeValue !== 'surcharge' || + busIncluded && this.modeValue === 'discount' + ) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/product_details_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/product_details_controller.js new file mode 100644 index 0000000..0c8b4d3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/product_details_controller.js @@ -0,0 +1,105 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch } from '../mixins/use_fetch' +import { useConfig } from '../mixins/use_config' + +export default class extends Controller { + + static targets = [ + 'loadingIndicator', + 'loadingMessage', + 'datesTableContainer', + 'priceTableContainer', + ] + + static values = { + datestableUri: String, + loading: Boolean, + loaded: Boolean, + mode: String, + disableTables: Boolean, + } + + initialize() { + useFetch(this) + useConfig(this) + } + + connect() { + if (false === this.disableTablesValue) { + this.loadDates() + } + } + + showDates() { + this.modeValue = 'dates' + } + + showPrices() { + this.modeValue = 'prices' + } + + switchView(e) { + let selectedView = e.detail + if ('dates' === selectedView) { + this.loadDates() + } + } + + loadDates() { + this.modeValue = 'dates' + if (true === this.loadedValue || true === this.disableTablesValue) { + return + } + this.loadingValue = true + let paCode = this.getConfig().paCode + const data = new URLSearchParams() + if (null !== paCode) { + data.append('pa', paCode) + } + this.datesTableContainerTarget.innerHTML = '' + fetch(`${this.datestableUriValue}&${data}`) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.datesTableContainerTarget.innerHTML = html + }).catch(() => { + }).finally(() => { + this.loadingValue = false + this.loadedValue = true + }) + } + + loadPrices({ params }) { + this.loadingValue = true + this.priceTableContainerTarget.innerHTML = '' + this.modeValue = 'prices' + let paCode = this.getConfig().paCode + const data = new URLSearchParams() + if (null !== paCode) { + data.append('pa', paCode) + } + fetch(`${params.pricesUrl}&${data}`) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.priceTableContainerTarget.innerHTML = html + }).catch(() => { + }).finally(() => { + this.loadingValue = false + }) + } + + loadingValueChanged() { + this.loadingMessageTarget.innerText = this.modeValue === 'prices' ? 'Lade Buchungsptionen...' : 'Lade Termine...' + this.loadingIndicatorTarget.classList.toggle('hidden', this.loadingValue === false) + } + + modeValueChanged() { + if (this.hasDatesTableContainerTarget) { + this.datesTableContainerTarget.classList.toggle('hidden', this.modeValue === 'prices') + } + if (this.hasPriceTableContainerTarget) { + this.priceTableContainerTarget.classList.toggle('hidden', this.modeValue === 'dates') + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_to_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_to_controller.js new file mode 100644 index 0000000..d6a5caa --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_to_controller.js @@ -0,0 +1,11 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static values = { target: String } + + scroll(e) { + e.preventDefault() + document.getElementById(this.targetValue).scrollIntoView({ behavior: 'smooth' }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_top_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_top_controller.js new file mode 100644 index 0000000..6f58730 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_top_controller.js @@ -0,0 +1,29 @@ +import { Controller } from '@hotwired/stimulus' +import { useDebounce } from 'stimulus-use' + +export default class extends Controller { + + static debounces = [ 'toggle' ] + static classes = [ 'hidden', 'visible' ] + + initialize() { + useDebounce(this) + } + + connect() { + window.addEventListener('scroll', () => { + this.toggle() + }, { passive: true }) + this.toggle() + } + + toggle() { + let isVisible = window.scrollY >= window.innerHeight / 3 + this.element.classList.toggle(this.hiddenClass, false === isVisible) + this.element.classList.toggle(this.visibleClass, isVisible) + } + + scroll() { + window.scroll({ top: 0, left: 0, behavior: 'smooth' }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/search_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/search_controller.js new file mode 100644 index 0000000..8132fb1 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/search_controller.js @@ -0,0 +1,59 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch } from '../mixins/use_fetch' + +export default class extends Controller { + + static values = { + loading: Boolean, + uri: String, + } + + static targets = [ + 'form', + 'resultsContainer', + 'loadingIndicator', + 'dateFrom', + 'dateTo', + ] + + initialize() { + useFetch(this) + } + + filter() { + this.load({ + method: 'POST', + body: new FormData(this.formTarget) + }) + } + + reset() { + this.load({ method: 'POST' }) + } + + load(options) { + this.loadingValue = true + fetch(this.uriValue, options) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.resultsContainerTarget.innerHTML = html + window.scroll({ top: 0, left: 0, behavior: 'smooth' }) + htmx.process(this.resultsContainerTarget) + }) + .finally(() => { + this.loadingValue = false + }) + } + + selectDateRange(e) { + this.dateFromTarget.value = e.detail.from + this.dateToTarget.value = e.detail.to + this.filter() + } + + loadingValueChanged() { + this.resultsContainerTarget.classList.toggle('opacity-25', this.loadingValue === true) + this.loadingIndicatorTarget.classList.toggle('hidden', this.loadingValue === false) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_controller.js new file mode 100644 index 0000000..0c182a6 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_controller.js @@ -0,0 +1,82 @@ +import { Controller } from '@hotwired/stimulus' +import { useDispatch } from 'stimulus-use' + +export default class extends Controller { + + static targets = [ + 'container', + 'dateFrom', + 'dateTo', + 'destinationUid', + 'destinationType', + 'pax', + 'priceRange', + 'dateRangeLabel', + 'paxLabel', + 'destinationLabel', + 'priceRangeLabel', + ] + + static values = { collapsed: Boolean } + + initialize() { + useDispatch(this) + } + + setDateRange(e) { + this.dateRangeLabelTarget.value = e.detail.label + this.dateFromTarget.value = e.detail.from + this.dateToTarget.value = e.detail.to + this.dispatch('input', this.getDetails()) + } + + setPax(e) { + e.preventDefault() + let pax = e.currentTarget.dataset.option + this.paxTarget.value = pax + this.paxLabelTarget.value = pax + this.dispatch('input', this.getDetails()) + } + + setDestination(e) { + e.preventDefault() + this.destinationUidTarget.value = e.currentTarget.dataset.optionUid + this.destinationTypeTarget.value = e.currentTarget.dataset.optionType + this.destinationLabelTarget.value = e.currentTarget.dataset.optionLabel + this.dispatch('input', this.getDetails()) + } + + resetDestination(e) { + e.preventDefault() + this.destinationUidTarget.value = '' + this.destinationTypeTarget.value = '' + this.destinationLabelTarget.value = '' + this.dispatch('input', this.getDetails()) + } + + setPriceRange(e) { + e.preventDefault() + this.priceRangeTarget.value = e.currentTarget.dataset.optionValue + this.priceRangeLabelTarget.value = e.currentTarget.dataset.labelValue + this.dispatch('input', this.getDetails()) + } + + getDetails() { + return { + dateFrom: this.dateFromTarget.value, + dateTo: this.dateToTarget.value, + pax: this.paxTarget.value, + destinationUid: this.destinationUidTarget.value, + destinationType: this.destinationTypeTarget.value, + priceRange: this.priceRangeTarget.value, + } + } + + toggle() { + this.collapsedValue = !this.collapsedValue + } + + collapsedValueChanged() { + this.containerTarget.classList.toggle('hidden', this.collapsedValue === true) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_toggle_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_toggle_controller.js new file mode 100644 index 0000000..623bca8 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_toggle_controller.js @@ -0,0 +1,14 @@ +import { Controller } from '@hotwired/stimulus' +import { useDispatch } from 'stimulus-use' + +export default class extends Controller { + + initialize() { + useDispatch(this) + } + + toggle(e) { + e.preventDefault() + this.dispatch('toggle') + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/slider_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/slider_controller.js new file mode 100644 index 0000000..bcdfc3d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/slider_controller.js @@ -0,0 +1,106 @@ +import { Controller } from '@hotwired/stimulus' +import Splide from '@splidejs/splide' + +const defaultOptions = { + arrows: false, + pagination: false, + perMove: 1, + gap: '1rem', + pauseOnHover: false, + pauseOnFocus: false, +} + +const presets = { + header: { + ...defaultOptions, + autoplay: true, + type: 'fade', + perPage: 1, + gap: 0, + rewind: true, + }, + content: { + ...defaultOptions, + rewind: true, + pagination: true, + arrows: true, + gap: 0, + classes: { + pagination: 'slider-pagination', + page: 'slider-pagination__dot', + }, + breakpoints: { + 768: { + pagination: false, + }, + }, + }, + teasers: { + ...defaultOptions, + perPage: 4, + padding: { + right: '2rem', + }, + breakpoints: { + 1200: { + perPage: 4, + }, + 1024: { + perPage: 3, + }, + 960: { + perPage: 2, + }, + 640: { + perPage: 1, + }, + }, + }, + teasersEmbedded: { + ...defaultOptions, + perPage: 2, + padding: { + right: '25%', + }, + breakpoints: { + 960: { + perPage: 3, + padding: { + right: '10%', + }, + }, + 768: { + perPage: 2, + padding: { + right: '25%', + }, + }, + 640: { + perPage: 2, + padding: { + right: '10%', + }, + }, + 512: { + perPage: 1, + padding: { + right: '10%', + }, + }, + }, + }, +} + +export default class extends Controller { + + static values = { preset: String } + + connect() { + let options = {} + let preset = this.presetValue + if (preset in presets) { + options = presets[preset] + } + new Splide(this.element, options).mount() + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/sticky_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/sticky_controller.js new file mode 100644 index 0000000..187af84 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/sticky_controller.js @@ -0,0 +1,54 @@ +import { Controller } from '@hotwired/stimulus' +import { useDebounce } from 'stimulus-use' + +export default class extends Controller { + + static debounces = [ 'toggle' ] + + static values = { + sticky: Boolean, + classes: String, + offset: Number, + padding: String, + disabled: Boolean, + } + + initialize() { + useDebounce(this) + } + + connect() { + window.addEventListener('scroll', () => { + this.toggle() + }, { passive: true }) + } + + disconnect() { + window.removeEventListener('scroll', () => { + this.toggle() + }, { passive: true }) + } + + toggle() { + if (true === this.disabledValue) { + return + } + this.stickyValue = window.scrollY > this.offsetValue + } + + stickyValueChanged(value) { + if (this.hasPaddingValue) { + if (true === value) { + document.body.style.paddingTop = this.paddingValue + } else { + document.body.removeAttribute('style') + } + } + if (!this.hasClassesValue) { + return + } + this.classesValue.split(' ').forEach(className => { + this.element.classList.toggle(className, this.stickyValue) + }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/tabs_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/tabs_controller.js new file mode 100644 index 0000000..0c620f3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/tabs_controller.js @@ -0,0 +1,34 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static classes = [ 'button', 'buttonActive' ] + + static targets = [ 'button', 'tab' ] + + static values = { activeTab: String } + + connect() { + this.activeTabValue = this.tabTargets[0].dataset.tab + } + + select(e) { + let selectedTab = e.currentTarget.dataset.tab + this.activeTabValue = selectedTab + this.dispatch('selected', { detail: selectedTab }) + this.dispatch('tab-' + selectedTab) + } + + activeTabValueChanged() { + let activeTab = this.activeTabValue + this.tabTargets.forEach(element => { + let currentTab = element.dataset.tab + element.classList.toggle('hidden', activeTab !== currentTab) + }) + this.buttonTargets.forEach(element => { + let currentButton = element.dataset.tab + element.classList.toggle(this.buttonClass, activeTab !== currentButton) + element.classList.toggle(this.buttonActiveClass, activeTab === currentButton) + }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/teasergrid_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/teasergrid_controller.js new file mode 100644 index 0000000..c58a0ec --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/teasergrid_controller.js @@ -0,0 +1,37 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static classes = [ 'grid' ] + + static targets = [ + 'button', + 'container', + ] + + static values = { disabled: Boolean } + + grid(e) { + e.preventDefault() + this.disabledValue = false + this.toggleButtonClass(e) + } + + block(e) { + e.preventDefault() + this.disabledValue = true + this.toggleButtonClass(e) + } + + toggleButtonClass(e) { + this.buttonTargets.forEach(element => { + element.classList.toggle('opacity-50', element === e.currentTarget) + }) + } + + disabledValueChanged() { + if (this.hasContainerTarget) { + this.containerTarget.classList.toggle(this.gridClass, this.disabledValue === false) + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_controller.js new file mode 100644 index 0000000..5999523 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_controller.js @@ -0,0 +1,37 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch } from '../mixins/use_fetch' + +export default class extends Controller { + + static targets = [ 'container', 'loadingIndicator' ] + + static values = { + uri: String, + loading: Boolean, + } + + initialize() { + useFetch(this) + } + + connect() { + this.load() + } + + load() { + this.loadingValue = true + let data = new URLSearchParams({ + 'tx_epproducts_ajax[watchlistUids]': sessionStorage.getItem('myep-watchlist'), + }) + fetch(this.uriValue, { method: 'POST', body: data }) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.containerTarget.innerHTML = html + htmx.process(this.containerTarget) + }) + .finally(() => { + this.loadingValue = false + }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_label_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_label_controller.js new file mode 100644 index 0000000..23bf909 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_label_controller.js @@ -0,0 +1,30 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static values = { count: Number } + + static targets = [ 'label', 'icon' ] + + connect() { + this.watchlist = JSON.parse(sessionStorage.getItem('myep-watchlist')) || [] + this.countValue = this.watchlist.length + } + + update(e) { + let key = e.detail.key + let index = this.watchlist.indexOf(key) + if (-1 !== index) { + this.watchlist.splice(index, 1) + } else { + this.watchlist.push(key) + } + this.countValue = this.watchlist.length + sessionStorage.setItem('myep-watchlist', JSON.stringify(this.watchlist)) + } + + countValueChanged() { + this.iconTarget.setAttribute('fill', this.countValue > 0 ? 'currentColor' : 'none') + this.labelTarget.innerText = `Merkliste (${this.countValue})` + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_toggle_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_toggle_controller.js new file mode 100644 index 0000000..5ae5e97 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_toggle_controller.js @@ -0,0 +1,45 @@ +import { Controller } from '@hotwired/stimulus' +import { useDispatch } from 'stimulus-use' + +export default class extends Controller { + + static values = { + key: String, + onList: Boolean + } + + static targets = [ + 'label', + 'icon', + 'ariaLabel', + ] + + initialize() { + useDispatch(this) + } + + connect() { + let watchlist = JSON.parse(sessionStorage.getItem('myep-watchlist')) || [] + this.onListValue = -1 !== watchlist.indexOf(this.keyValue) + } + + toggle() { + this.onListValue = !this.onListValue + this.dispatch('toggle', { + key: this.keyValue, + onList: this.onListValue, + }) + } + + onListValueChanged() { + if (this.hasLabelTarget) { + this.labelTarget.innerText = true === this.onListValue ? 'von der Merkliste entfernen' : 'auf die Merkliste setzen' + } + if (this.hasIconTarget) { + this.iconTarget.setAttribute('fill', true === this.onListValue ? 'currentColor' : 'none') + } + if (this.hasAriaLabelTarget) { + this.ariaLabelTarget.setAttribute('aria-label', true === this.onListValue ? 'von der Merkliste entfernen' : 'auf die Merkliste setzen') + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/main.js b/packages/ep_theme/Resources/Private/Assets/js/main.js new file mode 100644 index 0000000..7982173 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/main.js @@ -0,0 +1,5 @@ +// Main app +require('./_app'); + +// Main styles +require('../scss/main.scss'); diff --git a/packages/ep_theme/Resources/Private/Assets/js/mixins/use_config.js b/packages/ep_theme/Resources/Private/Assets/js/mixins/use_config.js new file mode 100644 index 0000000..acef946 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/mixins/use_config.js @@ -0,0 +1,11 @@ +export const useConfig = controller => { + Object.assign(controller, { + getConfig() { + const configElement = document.getElementById('appconfig') + if (null === configElement) { + return {} + } + return JSON.parse(configElement.innerHTML) + } + }) +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/mixins/use_fetch.js b/packages/ep_theme/Resources/Private/Assets/js/mixins/use_fetch.js new file mode 100644 index 0000000..904da32 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/mixins/use_fetch.js @@ -0,0 +1,19 @@ +export const useFetch = controller => { + Object.assign(controller, { + checkStatus(response) { + if (response.status >= 200 && response.status < 300) { + return response + } else { + const error = new Error(response.statusText) + error.response = response + throw error + } + }, + parseJSON(response) { + return response.json() + }, + parseHTML(response) { + return response.text() + } + }) +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/_base.scss b/packages/ep_theme/Resources/Private/Assets/scss/_base.scss new file mode 100644 index 0000000..72910bf --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/_base.scss @@ -0,0 +1,15 @@ +@use "sass:meta"; +@layer base { + body { + padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); + } + @include meta.load-css("base/typography"); + @include meta.load-css("base/images"); + + @include meta.load-css("base/rte-content"); + + iframe { + width: 100%; + height: 100vh; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/_components.scss b/packages/ep_theme/Resources/Private/Assets/scss/_components.scss new file mode 100644 index 0000000..107d601 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/_components.scss @@ -0,0 +1,15 @@ +@use "sass:meta"; +@layer components { + @include meta.load-css("components/topnav"); + @include meta.load-css("components/background"); + @include meta.load-css("components/badges"); + @include meta.load-css("components/buttons"); + @include meta.load-css("components/contact-bar"); + @include meta.load-css("components/calendar"); + @include meta.load-css("components/forms"); + @include meta.load-css("components/herounit"); + @include meta.load-css("components/misc"); + @include meta.load-css("components/searchbar"); + @include meta.load-css("components/teaser"); + @include meta.load-css("components/travelinfo"); +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/_utilities.scss b/packages/ep_theme/Resources/Private/Assets/scss/_utilities.scss new file mode 100644 index 0000000..f1293f5 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/_utilities.scss @@ -0,0 +1,40 @@ +@layer utilities { + .max-w-screen-2xl { + max-width: 1440px; + } + .max-w-1\/2 { + max-width: 50%; + } + .has-validation-error input { + @apply text-red-500 border-red-500; + } + .container-wide { + @apply container; + max-width: 1440px; + } + .underlined { + @apply relative mb-4; + + &:after { + content: ""; + @apply absolute top-100 left-0 mt-2 mb-2 border-b-4 w-48; + @apply border-ep-primary-dark sbw:border-sbw-primary-dark snz:border-snz-primary-dark suz:border-suz-primary-dark uch:border-uch-primary-dark; + } + } + .-translate-xy-1\/2 { + transform: translate(-50%, -50%); + } + .text-shadow { + text-shadow: 0 2px 2px theme('colors.zinc.400'); + } + .hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + border: 0; + } + + iframe:not([src]) { + display: none; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/_vendor.scss b/packages/ep_theme/Resources/Private/Assets/scss/_vendor.scss new file mode 100644 index 0000000..3b6656e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/_vendor.scss @@ -0,0 +1,143 @@ +@use "sass:meta"; + +// Lightbox +@import "~glightbox/dist/css/glightbox.css"; + +// Tooltips +@include meta.load-css("cooltipz-css/cooltipz.css"); + +:root { + --cooltipz-bg-color: theme('colors.zinc.700'); + --cooltipz-border-color: theme('colors.zinc.700'); + --cooltipz-text-color: theme('colors.white'); +} + +// Datepicker +@import "~flatpickr/dist/flatpickr.css"; + +[data-datepicker-inline-value="true"] .flatpickr-input { + @apply hidden; +} + +.flatpickr-calendar { + border: none #{!important}; + + &.inline { + @apply shadow-none #{!important}; + @apply border border-zinc-400 #{!important}; + } +} + +.flatpickr-day { + @apply rounded my-2 mx-0; + + &.available:not(.inRange) { + @apply text-white bg-emerald-500; + } +} + +.flatpickr-day.selected { + @apply bg-ep-secondary sbw:bg-sbw-secondary; + @apply border-ep-secondary sbw:border-sbw-secondary; + + &.startRange, &.endRange { + @apply bg-ep-secondary sbw:bg-sbw-secondary; + @apply border-ep-secondary sbw:border-sbw-secondary; + } +} + +// Price calendar (hand-rolled) +.pc-loading-indicator { + display: none; + + &.htmx-request { + display: flex; + } +} + +.pc-day { + @apply relative w-full text-center py-1 px-0.5 text-sm leading-tight rounded text-white cursor-pointer; + + // Status colors — [data-status] attribute adds specificity (0,2,0) + &[data-status="ok"] { + @apply bg-emerald-500; + } + &[data-status="blocked-to-ok"] { + background: linear-gradient(to bottom right, theme('colors.gray.400') 50%, theme('colors.emerald.500') 50%); + } + &[data-status="checkout-only"] { + background: linear-gradient(to bottom right, theme('colors.emerald.500') 50%, theme('colors.gray.400') 50%); + } + &[data-status="blocked"] { + @apply bg-gray-400 cursor-default; + } + &[data-status=""], &:not([data-status]) { + @apply text-gray-400 cursor-default; + } + + // Compound selector gives (0,2,0) specificity, matching [data-status] rules; source order wins. + &.pc-day--pending-start, + &.pc-day--selected-start, + &.pc-day--selected-end { + @apply bg-none outline-none bg-ep-secondary text-gray-700; + } + + &.pc-day--in-range { + @apply bg-none bg-ep-secondary text-gray-700 opacity-55; + } +} + +// Maps +@import "~leaflet/dist/leaflet.css"; + +.leaflet-pane { + z-index: 10 !important; +} +.leaflet-top, .leaflet-bottom, .leaflet-control { + z-index: 20 !important; +} + +// Slider +@import "~@splidejs/splide/dist/css/splide-core.min.css"; + +.slider-pagination { + @apply absolute bottom-0 left-1/2 transform -translate-x-1/2 md:mb-8; + @apply list-none p-0; + @apply flex items-center space-x-2; +} + +.slider-pagination__dot { + @apply hide-text block w-3 h-3 xl:w-4 xl:h-4 rounded-full bg-white shadow-md focus:outline-none; + + &.is-active { + @apply bg-ep-secondary; + } +} + +.slider-pagination__arrow { + @apply absolute z-10 text-ep-secondary bg-transparent focus:outline-none; + + .slider--teasers & { + @apply bg-ep-secondary text-white rounded p-1; + } + + &[disabled] { + @apply opacity-25; + } + + &--prev { + @apply left-0 top-1/2 transform -translate-y-1/2 ml-4; + + .slider--teasers & { + @apply -ml-8; + } + } + + &--next { + @apply right-0 top-1/2 transform -translate-y-1/2 mr-4; + + .slider--teasers & { + @apply -mr-8; + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/base/_fonts.scss b/packages/ep_theme/Resources/Private/Assets/scss/base/_fonts.scss new file mode 100644 index 0000000..b055ca2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/base/_fonts.scss @@ -0,0 +1,43 @@ +/* lato-300 - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: local(''), + url('../../fonts/lato-v17-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../../fonts/lato-v17-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +/* lato-regular - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local(''), + url('../../fonts/lato-v17-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../../fonts/lato-v17-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +/* lato-700 - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: local(''), + url('../../fonts/lato-v17-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../../fonts/lato-v17-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +/* lato-900 - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: local(''), + url('../../fonts/lato-v17-latin-900.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../../fonts/lato-v17-latin-900.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/base/_images.scss b/packages/ep_theme/Resources/Private/Assets/scss/base/_images.scss new file mode 100644 index 0000000..df2c609 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/base/_images.scss @@ -0,0 +1,5 @@ +.scale, +.img-responsive { + @apply block w-full h-auto; +} + diff --git a/packages/ep_theme/Resources/Private/Assets/scss/base/_rte-content.scss b/packages/ep_theme/Resources/Private/Assets/scss/base/_rte-content.scss new file mode 100644 index 0000000..e91946a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/base/_rte-content.scss @@ -0,0 +1,86 @@ +// Self-contained: the wrapper lives here so `&` and @extend resolve the same +// way they did when this file was @imported into a [data-rte-content] block. +[data-rte-content] { + h1, + .headline--primary, + h2, + .headline--secondary, + h3, + .headline--3, + h4, + .headline--4 { + @apply mb-4; + } + + ul { + @apply list-disc pl-4 mb-4; + } + + ol { + @apply list-decimal pl-4 mb-4; + } + + p { + @apply mb-4; + } + + a:not(.button):not(.rte-button):not(.rte-button--full) { + @apply underline text-ep-primary-dark sbw:text-sbw-primary-dark; + + .bg-brand-gradient & { + @apply text-white underline; + } + } + + a.rte-button { + @apply inline-flex items-center justify-center px-4 py-2 md:px-8; + @apply uppercase leading-none text-center; + @apply transition-colors outline-none; + @apply bg-ep-primary-light text-white; + @apply sbw:bg-sbw-secondary; + @apply snz:bg-snz-secondary; + @apply suz:bg-suz-secondary; + @apply uch:bg-uch-secondary; + @apply ser:bg-ser-secondary; + @apply hover:bg-ep-secondary group-hover:bg-ep-secondary; + @apply sbw:hover:bg-sbw-primary-dark sbw:group-hover:bg-sbw-primary-dark; + @apply snz:hover:bg-snz-primary-dark snz:group-hover:bg-snz-primary-dark; + @apply suz:hover:bg-suz-primary-dark suz:group-hover:bg-suz-primary-dark; + @apply uch:hover:bg-uch-primary-dark uch:group-hover:bg-uch-primary-dark; + @apply ser:hover:bg-ser-primary ser:group-hover:bg-ser-primary; + @apply focus:outline focus:outline-2 focus:outline-offset-2; + @apply focus:outline-ep-primary-dark sbw:focus:outline-sbw-secondary uch:focus:outline-uch-secondary; + @apply suz:focus:outline-suz-secondary ser:focus:outline-ser-secondary snz:focus:outline-snz-secondary; + + } + + a.rte-button--full { + @extend .rte-button; + @apply w-full; + } + + table, + .table { + @apply w-full; + } + + thead { + tr { + @apply bg-zinc-200; + } + } + + tbody { + tr { + @apply odd:bg-zinc-100; + } + } + + th { + @apply border-r border-white/50 px-2 py-1 md:p-2 text-left text-sm md:text-base; + } + + td { + @apply border-r border-zinc-200 px-2 py-1 md:p-2; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/base/_typography.scss b/packages/ep_theme/Resources/Private/Assets/scss/base/_typography.scss new file mode 100644 index 0000000..323f077 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/base/_typography.scss @@ -0,0 +1,49 @@ +h1, +.headline--primary { + @apply block text-3xl md:text-4xl font-bold mb-2; +} + +h2, +.headline--secondary { + @apply block text-2xl md:text-3xl font-bold mb-2; +} + +h3, +.headline--3 { + @apply block text-xl md:text-2xl font-bold leading-6 mb-2; +} + +h4, +.headline--4 { + @apply block md:text-lg font-bold; +} + +.headline--underlined:after { + @apply block border-b-4 border-ep-primary-light pb-4 mb-4 -mt-2 w-20; + @apply sbw:border-sbw-primary snz:border-snz-primary suz:border-suz-primary uch:border-uch-primary ser:border-ser-secondary; + content: ''; +} + +.subline { + @apply block text-sm md:text-base font-normal leading-6; +} + +// Lists +.check-list { + @apply list-none p-0; + + li { + @apply relative pl-8; + &:before { + @apply absolute left-0 top-0 w-6 h-6 text-ep-primary-dark text-lg; + font-family: 'fontello', sans-serif; + content: '\e800'; + } + } + + &--inverted { + li:before { + content: '\e802'; + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_background.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_background.scss new file mode 100644 index 0000000..b1e9485 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_background.scss @@ -0,0 +1,21 @@ +.bg-winter { + @apply bg-cover bg-no-repeat bg-center; + background-image: url(../../images/background/winter.jpg); +} + +.bg-pattern { + @apply bg-cover bg-no-repeat bg-center; + background-image: url(../../images/background/pattern.jpg); +} + +.bg-header-season-w { + @screen md { + background-image:url('../../images/background/header-winter.jpg'); + } +} + +.bg-header-season-s { + @screen md { + background-image: url('../../images/background/header-sommer.jpg'); + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_badges.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_badges.scss new file mode 100644 index 0000000..03c10cf --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_badges.scss @@ -0,0 +1,51 @@ +.concept-badge { + @apply w-full h-8 text-white px-4 leading-none uppercase bg-concept-default; + + &--bdg { + @apply bg-concept-bdg; + } + + &--skr { + @apply bg-concept-skr; + } + + &--evt { + @apply bg-concept-evt; + } + + &--fam { + @apply bg-concept-fam; + } + + &--grp { + @apply bg-concept-grp; + } + + &--prg { + @apply bg-concept-prg; + } + + &--ind { + @apply bg-concept-ind; + } + + &--wkd { + @apply bg-concept-wkd; + } + + &--kt { + @apply bg-concept-kt; + } + + &--spz { + @apply bg-concept-spz; + } + + &--epe { + @apply bg-concept-epe; + } + + &--sfl { + @apply bg-concept-sfl; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_buttons.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_buttons.scss new file mode 100644 index 0000000..b64c6aa --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_buttons.scss @@ -0,0 +1,100 @@ +.button { + @apply inline-flex items-center justify-center space-x-2 px-4 py-2 md:px-8 cursor-pointer; + @apply uppercase leading-none text-center; + @apply transition-colors outline-none; + @apply focus:outline focus:outline-2 focus:outline-offset-2; + @apply focus:outline-ep-primary-dark sbw:focus:outline-sbw-secondary uch:focus:outline-uch-secondary; + @apply suz:focus:outline-suz-secondary ser:focus:outline-ser-secondary snz:focus:outline-snz-secondary; + + &--small { + @apply px-2 py-1 md:px-4 text-xs sm:text-sm; + } + + &--full { + @apply block w-full; + } +} + +.teaser-button { + @apply w-full; + @apply bg-ep-primary-dark text-white; + @apply sbw:bg-sbw-secondary; + @apply snz:bg-snz-secondary; + @apply suz:bg-suz-secondary; + @apply uch:bg-uch-secondary; + @apply ser:bg-ser-secondary; + @apply hover:bg-badge-gradient hover:text-white; + @apply peer-hover:bg-badge-gradient peer-hover:text-white; + @apply sbw:hover:bg-none sbw:hover:bg-sbw-primary-dark; + @apply snz:hover:bg-none snz:hover:bg-snz-primary-dark; + @apply suz:hover:bg-none suz:hover:bg-suz-primary-dark; + @apply uch:hover:bg-none uch:hover:bg-uch-primary-dark; + @apply ser:hover:bg-none ser:hover:bg-ser-primary; + @apply sbw:peer-hover:bg-none sbw:peer-hover:bg-sbw-primary-dark; + @apply snz:peer-hover:bg-none snz:peer-hover:bg-snz-primary-dark; + @apply suz:peer-hover:bg-none suz:peer-hover:bg-suz-primary-dark; + @apply uch:peer-hover:bg-none uch:peer-hover:bg-uch-primary-dark; + @apply ser:peer-hover:bg-none ser:peer-hover:bg-ser-primary; +} + +.bg-button { + @apply bg-ep-primary-dark text-white; + @apply sbw:bg-sbw-secondary; + @apply snz:bg-snz-secondary; + @apply suz:bg-suz-secondary; + @apply uch:bg-uch-secondary; + @apply ser:bg-ser-secondary; + @apply hover:bg-badge-gradient hover:text-white; + @apply sbw:hover:bg-none sbw:hover:bg-sbw-primary-dark; + @apply snz:hover:bg-none snz:hover:bg-snz-primary-dark; + @apply suz:hover:bg-none suz:hover:bg-suz-primary-dark; + @apply uch:hover:bg-none uch:hover:bg-uch-primary-dark; + @apply ser:hover:bg-none ser:hover:bg-ser-primary; + + &--active, + &--secondary { + @apply bg-badge-gradient text-white; + @apply sbw:bg-none sbw:bg-sbw-secondary; + @apply snz:bg-none snz:bg-snz-secondary; + @apply suz:bg-none suz:bg-suz-secondary; + @apply uch:bg-none uch:bg-uch-secondary; + @apply ser:bg-none ser:bg-ser-secondary; + @apply hover:bg-none hover:bg-ep-pink hover:text-white; + @apply sbw:hover:bg-none sbw:hover:bg-sbw-primary-dark; + @apply snz:hover:bg-none snz:hover:bg-snz-primary-dark; + @apply suz:hover:bg-none suz:hover:bg-suz-primary-dark; + @apply uch:hover:bg-none uch:hover:bg-uch-primary-dark; + @apply ser:hover:bg-none ser:hover:bg-ser-primary; + } + + &--muted { + @apply bg-zinc-300 text-gray-900 hover:bg-zinc-300; + } + + &--dark { + @apply bg-ep-primary-dark; + @apply ser:bg-ser-primary; + @apply ser:hover:bg-ser-secondary; + } + + &--light { + @apply bg-white border-2; + @apply font-bold; + @apply border-ep-primary-dark text-ep-primary-dark; + @apply sbw:bg-white sbw:border-sbw-primary sbw:text-sbw-primary; + @apply snz:bg-white snz:border-snz-primary snz:text-snz-primary; + @apply suz:bg-white suz:border-suz-primary suz:text-suz-primary; + @apply uch:bg-white uch:border-uch-primary uch:text-uch-primary; + @apply ser:bg-white ser:border-ser-primary ser:text-ser-primary; + + &:hover, + &.button--active { + @apply bg-none bg-ep-pink border-ep-pink; + @apply sbw:bg-white sbw:border-sbw-secondary sbw:text-sbw-secondary; + @apply snz:bg-white snz:border-snz-secondary snz:text-snz-secondary; + @apply suz:bg-white suz:border-suz-secondary suz:text-suz-secondary; + @apply uch:bg-white uch:border-uch-secondary uch:text-uch-secondary; + @apply ser:bg-white ser:border-ser-secondary ser:text-ser-secondary; + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_calendar.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_calendar.scss new file mode 100644 index 0000000..c0aac25 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_calendar.scss @@ -0,0 +1,35 @@ +.availability-full { + @apply bg-emerald-500; +} + +.availability-partial { + @apply bg-amber-300; +} + +.availability-none { + @apply bg-red-500; +} + +.availability-full-to-none { + @apply bg-gradient-to-br from-emerald-500 to-red-500; +} + +.availability-full-to-partial { + @apply bg-gradient-to-br from-emerald-500 to-amber-300; +} + +.availability-none-to-full { + @apply bg-gradient-to-br from-red-500 to-emerald-500; +} + +.availability-partial-to-full { + @apply bg-gradient-to-br from-amber-300 to-emerald-500; +} + +.availability-none-to-partial { + @apply bg-gradient-to-br from-red-500 to-amber-300; +} + +.availability-partial-to-none { + @apply bg-gradient-to-br from-amber-300 to-red-500; +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_contact-bar.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_contact-bar.scss new file mode 100644 index 0000000..57f9e24 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_contact-bar.scss @@ -0,0 +1,37 @@ +.contact-bar { + @apply fixed z-40 bottom-0 left-0 w-full flex justify-between; + + @screen md { + @apply bottom-auto left-auto top-1/2 right-0 w-auto flex-col divide-x-0 space-y-1; + transform: translate(calc(100% - theme('spacing.8')), -50%); + } +} + +.contact-bar__item { + @apply flex-1 text-white py-2 bg-ep-primary-dark border-r border-white last:border-0; + @apply ser:bg-ser-primary sbw:bg-sbw-secondary snz:bg-snz-primary suz:bg-suz-primary uch:bg-uch-primary; + + @screen md { + @apply flex flex-none items-start transition-transform duration-300 ease-in-out p-1 shadow-md focus:text-white border-0; + &:hover { + @apply text-white; + transform: translateX(calc(-100% + theme('spacing.8'))); + } + } +} + +.contact-bar__icon { + @apply w-6 h-6 mx-auto; + + @screen md { + @apply mx-0 mr-2; + } +} + +.contact-bar__label { + @apply hidden; + + @screen md { + @apply block; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_forms.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_forms.scss new file mode 100644 index 0000000..793df8c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_forms.scss @@ -0,0 +1,22 @@ +.form-field { + @apply border-zinc-400 focus:border-zinc-800 ring-0 block w-full mt-1; + @apply focus:ring-0 focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-zinc-800; + + &--has-error { + @apply border-red-500; + } +} + +.form-checkbox, +.form-radio { + @apply border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 text-ep-secondary sbw:text-sbw-secondary uch:text-uch-secondary h-4 w-4; + @apply focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-zinc-800; + + &--has-error { + @apply border-red-500; + } +} + +input[disabled] { + @apply cursor-not-allowed; +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_herounit.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_herounit.scss new file mode 100644 index 0000000..4f4895b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_herounit.scss @@ -0,0 +1,20 @@ +.herounit { + @apply relative w-full max-w-screen-2xl mx-auto overflow-hidden; +} + +.herounit__badge-wrapper { + @apply absolute top-0 right-0 md:right-32 w-full px-4 md:w-auto md:px-0; + @apply flex flex-row-reverse justify-between md:flex-col items-start md:items-center md:space-y-4; +} + +.herounit__badge { + @apply text-white; + + &--skipass { + @apply bg-badge-gradient ser:bg-none ser:bg-ser-secondary py-8 px-2; + } + + &--custom { + @apply mt-4 w-40 transform rotate-6; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_misc.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_misc.scss new file mode 100644 index 0000000..c0bfaa1 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_misc.scss @@ -0,0 +1,11 @@ +.headerbar { + @apply bg-ep-primary-light sbw:bg-sbw-primary text-white uppercase font-semibold py-2 px-4 mb-4; +} + +.anchor { + @apply block h-0 w-0 pt-16 -mt-16; +} + +.alert { + @apply bg-ep-primary-bg p-4 text-ep-primary-dark; +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_searchbar.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_searchbar.scss new file mode 100644 index 0000000..32f734a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_searchbar.scss @@ -0,0 +1,11 @@ +.searchbar { + @apply hidden relative lg:block lg:container -mt-20 mb-8; + + &.sticky { + @apply fixed z-50 top-8 left-0 mt-0 mb-0 inset-x-0 shadow-lg; + } + + &.reduced { + @apply max-w-full px-0 mt-0 mb-0; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_teaser.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_teaser.scss new file mode 100644 index 0000000..8a38b93 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_teaser.scss @@ -0,0 +1,73 @@ +.product-teaser__flag { + @apply w-6 h-4 md:w-8 md:h-6; +} + +.product-teaser__label-concept { + @apply text-white uppercase font-semibold; + + [data-teasergrid-disabled-value="false"] & { + @apply text-xs md:text-base; + } +} + +.product-teaser__watchlist-icon { + @apply text-white w-6 h-6; + + [data-teasergrid-disabled-value="false"] & { + @apply w-4 h-4 md:w-6 md:h-6; + } +} + +.product-teaser__label-name { + @apply text-zinc-800 leading-none; + + [data-teasergrid-disabled-value="false"] & { + @apply text-sm md:text-base; + } +} + +.product-teaser__label-region { + @apply h-12 md:h-16 text-zinc-800 leading-none font-bold text-3xl; + + [data-teasergrid-disabled-value="false"] & { + @apply text-lg leading-none md:text-3xl md:leading-none; + } +} + +.product-teaser__label-included { + @apply pb-2 text-zinc-800 text-xl font-bold leading-none; + + [data-teasergrid-disabled-value="false"] & { + @apply text-sm md:text-lg lg:text-xl; + } +} + +.product-teaser__list-included { + [data-teasergrid-disabled-value="false"] & { + @apply text-sm md:text-base; + } +} + +.product-teaser__list-icon { + @apply w-6 h-6 text-ep-primary; + + [data-teasergrid-disabled-value="false"] & { + @apply w-4 h-4 md:w-6 md:h-6; + } +} + +.product-teaser__price { + @apply text-3xl; + + [data-teasergrid-disabled-value="false"] & { + @apply text-xl md:text-3xl; + } +} + +.product-teaser__label-price { + @apply inline-block; + + [data-teasergrid-disabled-value="false"] & { + @apply text-sm md:text-base lg:inline-block; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_topnav.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_topnav.scss new file mode 100644 index 0000000..efd5fe3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_topnav.scss @@ -0,0 +1,20 @@ +.topnav { + @apply hidden lg:block w-full fixed top-0 inset-x-0 z-50 bg-ep-primary-medium ser:bg-ser-primary shadow; +} + +.topnav__nav { + @apply flex items-center m-0 p-0; +} + +.topnav__item { + @apply flex items-center justify-center w-16 h-8 text-white hover:text-white focus:text-white hover:bg-ep-secondary; + @apply ser:hover:bg-ser-secondary ser:hover:text-white; + @apply sbw:hover:bg-sbw-secondary sbw:hover:text-white; + @apply border-r border-white/50; +} + +.topnav__item--active { + @apply bg-ep-secondary text-gray-900; + @apply sbw:bg-sbw-secondary sbw:text-white; + @apply ser:bg-ser-secondary ser:text-white; +} \ No newline at end of file diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_travelinfo.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_travelinfo.scss new file mode 100644 index 0000000..b0bfdff --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_travelinfo.scss @@ -0,0 +1,19 @@ +.travel-info-box { + @apply flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white px-8 lg:px-0 py-8; +} + +.travel-info-box-title { + @apply lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8; +} + +.travel-info-box-title__inner { + @apply text-2xl lg:text-4xl text-white uppercase font-bold; +} + +.travel-info-box-content { + @apply lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white; +} + +.travel-info-box a { + color: inherit !important; +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/main.scss b/packages/ep_theme/Resources/Private/Assets/scss/main.scss new file mode 100644 index 0000000..5dc7023 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/main.scss @@ -0,0 +1,18 @@ +@use "sass:meta"; + +// Outside the cascade layers on purpose: @font-face is invalid inside @layer, which is why +// _base.scss leaves base/fonts out. Loading it here lets Webpack resolve the url()s to +// content-hashed filenames, and replaces the hand-maintained @font-face block that used to +// live in ep_theme Page/Main.typoscript. +@include meta.load-css("base/fonts"); + +@include meta.load-css("tailwindcss/base"); +@include meta.load-css("base"); + +@include meta.load-css("tailwindcss/components"); +@include meta.load-css("components"); + +@include meta.load-css("tailwindcss/utilities"); +@include meta.load-css("utilities"); + +@include meta.load-css("vendor"); diff --git a/packages/ep_theme/Resources/Private/Assets/scss/rte.scss b/packages/ep_theme/Resources/Private/Assets/scss/rte.scss new file mode 100644 index 0000000..5fd3059 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/rte.scss @@ -0,0 +1,30 @@ +@use "base/fonts"; + +body { + @apply antialiased text-zinc-800 font-sans; +} + +table { + border: 1px solid #ccc; + border-collapse: collapse; + font-size: inherit; +} + +a.rte-button { + @apply inline-flex items-center justify-center px-4 py-2 md:px-8 cursor-pointer; + @apply uppercase leading-none text-center no-underline; + @apply bg-ep-primary-dark text-white; +} + +a.rte-button--full { + @extend .rte-button; + @apply w-full; +} + +td, th { + border: 1px solid #ccc; + border-collapse: collapse; + vertical-align: top; + text-align: left; + padding: 3px; +} diff --git a/packages/ep_theme/Resources/Private/Forms/AdresscheckForm.form.yaml b/packages/ep_theme/Resources/Private/Forms/AdresscheckForm.form.yaml new file mode 100644 index 0000000..f7241ed --- /dev/null +++ b/packages/ep_theme/Resources/Private/Forms/AdresscheckForm.form.yaml @@ -0,0 +1,144 @@ + +renderingOptions: + submitButtonLabel: Absenden +type: Form +identifier: adresscheck-ext +label: Adresscheck (EXT) +prototypeName: standard +finishers: +- + options: + subject: Adresscheck + recipientAddress: 'info@ep-reisen.de' + recipientName: 'E&P Reisen' + senderAddress: smtprelay@ep-reisen.de + senderName: 'E&P Reisen' + replyToAddress: '' + carbonCopyAddress: '' + blindCarbonCopyAddress: '' + format: html + attachUploads: false + translation: + language: '' + identifier: EmailToReceiver +- + identifier: SaveToDatabase + options: + - + table: 'tx_eptheme_domain_model_addresscheck' + mode: insert + databaseColumnMappings: + pid: + value: 1228 + elements: + customer-id: + mapOnDatabaseColumn: 'customer_id' + title: + mapOnDatabaseColumn: 'title' + first-name: + mapOnDatabaseColumn: 'first_name' + last-name: + mapOnDatabaseColumn: 'last_name' + address: + mapOnDatabaseColumn: 'address' + zip: + mapOnDatabaseColumn: 'zip' + city: + mapOnDatabaseColumn: 'city' + country: + mapOnDatabaseColumn: 'country' + email: + mapOnDatabaseColumn: 'email' + no-postal: + mapOnDatabaseColumn: 'no_postal' + no-email: + mapOnDatabaseColumn: 'no_email' + address-is-current-postal: + mapOnDatabaseColumn: 'address_current' +- + options: + pageUid: '1606' + additionalParameters: '' + identifier: Redirect +renderables: +- + renderingOptions: + previousButtonLabel: '' + nextButtonLabel: '' + type: Page + identifier: page-1 + label: '' + renderables: + - + defaultValue: '' + type: Hidden + identifier: customer-id + label: KundenID + - + defaultValue: '' + type: Text + identifier: title + label: Anrede + - + defaultValue: '' + type: Text + identifier: first-name + label: Vorname + properties: + fluidAdditionalAttributes: + required: required + - + defaultValue: '' + type: Text + identifier: last-name + label: Name + properties: + fluidAdditionalAttributes: + required: required + - + defaultValue: '' + type: Text + identifier: address + label: 'Straße & Hausnummer' + properties: + elementDescription: '' + - + defaultValue: '' + type: Text + identifier: zip + label: PLZ + - + defaultValue: '' + type: Text + identifier: city + label: Stadt + - + defaultValue: '' + type: Text + identifier: country + label: Land + - + defaultValue: '' + type: Text + identifier: email + label: E-Mail + properties: + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty + - + identifier: EmailAddress + - + type: Checkbox + identifier: no-postal + label: 'Postalische Werbung abbestellen' + - + type: Checkbox + identifier: no-email + label: 'Ich möchte keine Mails mehr erhalten.' + - + type: Checkbox + identifier: address-is-current + label: 'Meine Adresse ist aktuell.' diff --git a/packages/ep_theme/Resources/Private/Forms/AdresscheckRemove.form.yaml b/packages/ep_theme/Resources/Private/Forms/AdresscheckRemove.form.yaml new file mode 100644 index 0000000..f3d8627 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Forms/AdresscheckRemove.form.yaml @@ -0,0 +1,118 @@ + +renderingOptions: + submitButtonLabel: aus Mailingliste entfernen +type: Form +identifier: adresscheck-remove-ext +label: Adresscheck aus Datenbank entfernen (EXT) +prototypeName: standard +finishers: +- + options: + subject: 'Adresscheck - aus Datenbank entfernen' + recipientAddress: 'info@ep-reisen.de' + recipientName: 'E&P Reisen' + senderAddress: smtprelay@ep-reisen.de + senderName: 'E&P Reisen' + replyToAddress: '' + carbonCopyAddress: '' + blindCarbonCopyAddress: '' + format: html + attachUploads: false + translation: + language: '' + identifier: EmailToReceiver +- + identifier: SaveToDatabase + options: + - + table: 'tx_eptheme_domain_model_addresscheck' + mode: insert + databaseColumnMappings: + pid: + value: 1609 + elements: + customer-id: + mapOnDatabaseColumn: 'customer_id' + title: + mapOnDatabaseColumn: 'title' + first-name: + mapOnDatabaseColumn: 'first_name' + last-name: + mapOnDatabaseColumn: 'last_name' + address: + mapOnDatabaseColumn: 'address' + zip: + mapOnDatabaseColumn: 'zip' + city: + mapOnDatabaseColumn: 'city' + country: + mapOnDatabaseColumn: 'country' + email: + mapOnDatabaseColumn: 'email' + no-postal: + mapOnDatabaseColumn: 'no_postal' + no-email: + mapOnDatabaseColumn: 'no_email' + address-is-current-postal: + mapOnDatabaseColumn: 'address_current' +- + options: + pageUid: '1610' + additionalParameters: '' + identifier: Redirect +renderables: +- + renderingOptions: + previousButtonLabel: '' + nextButtonLabel: '' + type: Page + identifier: page-1 + label: '' + renderables: + - + defaultValue: '' + type: Hidden + identifier: customer-id + label: KundenID + - + defaultValue: '' + type: Hidden + identifier: title + label: Anrede + - + defaultValue: '' + type: Hidden + identifier: first-name + label: Vorname + - + defaultValue: '' + type: Hidden + identifier: last-name + label: Name + - + defaultValue: '' + type: Hidden + identifier: address + label: 'Straße & Hausnummer' + properties: + elementDescription: '' + - + defaultValue: '' + type: Hidden + identifier: zip + label: PLZ + - + defaultValue: '' + type: Hidden + identifier: city + label: Stadt + - + defaultValue: '' + type: Hidden + identifier: country + label: Land + - + defaultValue: '' + type: Hidden + identifier: email + label: E-Mail diff --git a/packages/ep_theme/Resources/Private/Forms/ApplicationForm.form.yaml b/packages/ep_theme/Resources/Private/Forms/ApplicationForm.form.yaml new file mode 100644 index 0000000..be73b78 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Forms/ApplicationForm.form.yaml @@ -0,0 +1,378 @@ + +renderingOptions: + submitButtonLabel: Absenden +type: Form +identifier: applicationform +label: Bewerbungsformular +prototypeName: applicationform +finishers: + - + options: + subject: 'Bewerbung von {text-1}' + recipientAddress: team@ep-reisen.de + recipientName: 'E&P Reisen' + senderAddress: team@ep-reisen.de + senderName: '{text-1}' + replyToAddress: '{text-6}' + carbonCopyAddress: '' + blindCarbonCopyAddress: '' + format: html + attachUploads: false + translation: + language: '' + identifier: EmailToReceiver + - + options: + message: 'Vielen Dank für deine Bewerbung. Wir werden sie so schnell wie möglich bearbeiten.' + identifier: Confirmation +renderables: + - + renderingOptions: + previousButtonLabel: zurück + nextButtonLabel: weiter + type: Page + identifier: page-1 + label: '' + renderables: + - + defaultValue: '' + type: Text + identifier: text-1 + label: Name + properties: + fluidAdditionalAttributes: + placeholder: 'Vor- & Nachname' + required: required + elementDescription: '' + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: text-2 + label: Straße + properties: + fluidAdditionalAttributes: + placeholder: 'Straße & Hausnummer' + required: required + elementDescription: '' + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: text-3 + label: Wohnort + properties: + fluidAdditionalAttributes: + placeholder: 'PLZ & Ort' + required: required + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: text-4 + label: 'Mobil Nummer' + properties: + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: text-5 + label: 'Festnetz Nummer' + - + defaultValue: '' + type: Text + identifier: text-6 + label: 'E-Mail Adresse' + properties: + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty + - + identifier: EmailAddress + - + defaultValue: '' + type: Text + identifier: text-7 + label: Geburtsdatum + properties: + fluidAdditionalAttributes: + placeholder: tt.mm.jjjj + required: required + validators: + - + options: + regularExpression: '/\d{2}\.\d{2}\.\d{4}/' + identifier: RegularExpression + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: text-8 + label: Nationalität + - + properties: + options: + nein: nein + ja: ja + 'ja, mit Anhänger': 'ja, mit Anhänger' + type: RadioButton + identifier: radiobutton-1 + label: Führerschein + - + defaultValue: '' + type: Text + identifier: text-9 + label: Ausbildung/Studium + properties: + fluidAdditionalAttributes: + placeholder: 'Schul-, Uni-, Berufsausbildung' + elementDescription: '' + - + defaultValue: '' + type: Text + identifier: text-10 + label: 'Aktuelle Tätigkeit' + - + properties: + options: + Haus-/Destinationsleitung: Haus-/Destinationsleitung + Koch/Köchin: Koch/Köchin + Koch-Assistenz/Service: Koch-Assistenz/Service + 'Serviceleitung/Assistenz Hausleitung': 'Serviceleitung/Assistenz Hausleitung' + Serviceteamer: Serviceteamer + Reinigungskraft: Reinigungskraft + Materialteamer/Verleih: Materialteamer/Verleih + Allrounder: Allrounder + 'Praktikum im Schnee': 'Praktikum im Schnee' + elementDescription: '' + type: MultiCheckbox + identifier: multicheckbox-1 + label: 'Bewerbung als' + - + defaultValue: '' + type: Text + identifier: text-11 + label: 'Einsatz datum' + properties: + fluidAdditionalAttributes: + placeholder: 'von tt.mm.jjjj bis tt.mm.jjjj' + required: required + validators: + - + identifier: NotEmpty + - + properties: + options: + Ski: Ski + Snowboard: Snowboard + nichts: nichts + type: MultiCheckbox + identifier: multicheckbox-2 + label: 'Ich fahre' + validators: + - + identifier: NotEmpty + - + options: + minimum: '1' + maximum: '2' + identifier: Count + - + properties: + options: + 'noch nicht gelernt': 'noch nicht gelernt' + Anfänger: Anfänger + Fortgeschritten: Fortgeschritten + Experte: Experte + type: SingleSelect + identifier: singleselect-1 + label: 'Wie gut fahre ich Ski' + defaultValue: 'noch nicht gelernt' + - + defaultValue: '' + type: Text + identifier: text-12 + label: 'Skifahrer seit' + - + properties: + options: + 'noch nicht gelernt': 'noch nicht gelernt' + Anfänger: Anfänger + Fortgeschritten: Fortgeschritten + Experte: Experte + type: SingleSelect + identifier: singleselect-2 + label: 'Wie gut fahre ich Snowboard' + - + defaultValue: '' + type: Text + identifier: text-13 + label: 'Snowboarder seit' + properties: + elementDescription: '' + - + properties: + options: + nein: nein + 'Ski Grundstufe': 'Ski Grundstufe' + 'Board Grundstufe': 'Board Grundstufe' + 'Ski Instructor': 'Ski Instructor' + 'Board Instructor': 'Board Instructor' + 'Ski Begleitschein': 'Ski Begleitschein' + 'Board Begleitschein': 'Board Begleitschein' + type: MultiSelect + identifier: multiselect-1 + label: Ski-/Snowboardlehrerschein + defaultValue: + - nein + - + type: Fieldset + identifier: fieldset-1 + label: Fremdsprachen + renderables: + - + properties: + options: + nein: nein + Grundkenntnisse: Grundkenntnisse + gut: gut + 'sehr gut': 'sehr gut' + type: SingleSelect + identifier: singleselect-3 + label: Englisch + defaultValue: nein + - + properties: + options: + nein: nein + Grundkenntnisse: Grundkenntnisse + gut: gut + 'sehr gut': 'sehr gut' + type: SingleSelect + identifier: singleselect-4 + label: Französisch + defaultValue: nein + - + properties: + options: + nein: nein + Grundkenntnisse: Grundkenntnisse + gut: gut + 'sehr gut': 'sehr gut' + type: SingleSelect + identifier: singleselect-5 + label: Spanisch + defaultValue: nein + - + defaultValue: '' + type: Text + identifier: text-14 + label: 'Sonstige Fremdsprachen' + - + properties: + options: + ja: ja + nein: nein + type: RadioButton + identifier: radiobutton-2 + label: 'Kocherfahrung für Gruppen (min. 30 Personen)' + - + defaultValue: '' + type: Text + identifier: text-15 + label: 'Wenn ja, welche?' + properties: + elementDescription: '' + - + properties: + options: + ja: ja + nein: nein + type: RadioButton + identifier: radiobutton-3 + label: 'Erfahrungen im Tourismus' + - + defaultValue: '' + type: Text + identifier: text-16 + label: 'Wenn ja, welche?' + properties: + elementDescription: '' + - + properties: + options: + 'ja, mit Zertifikat zur Bindungseinstellung': 'ja, mit Zertifikat zur Bindungseinstellung' + nein: nein + type: RadioButton + identifier: radiobutton-4 + label: 'Erfahrungen im Skiverleih' + - + properties: + options: + Zillertal: Zillertal + Lenzerheide: Lenzerheide + 'Davos Klosters': 'Davos Klosters' + Montafon: Montafon + Saalbach: Saalbach + 'Les Deux Alpes': 'Les Deux Alpes' + '4 Vallées': '4 Vallées' + 'Val Thorens': 'Val Thorens' + 'Les Sybelles': 'Les Sybelles' + 'Portes du Soleil': 'Portes du Soleil' + type: MultiCheckbox + identifier: multicheckbox-3 + label: 'Ortskenntnisse in folgenden Destinationen' + - + properties: + options: + 'Damen 36': 'Damen 36' + 'Damen 38': 'Damen 38' + 'Damen 40': 'Damen 40' + 'Damen 42': 'Damen 42' + 'Herren S': 'Herren S' + 'Herren M': 'Herren M' + 'Herren L': 'Herren L' + 'Herren XL': 'Herren XL' + type: SingleSelect + identifier: singleselect-6 + label: Jackengröße + - + defaultValue: '' + type: Textarea + identifier: textarea-1 + label: 'Warum möchte ich für E&P Reisen arbeiten?' + - + properties: + options: + Freunde: Freunde + 'Social Media': 'Social Media' + Uni: Uni + Sonstiges: Sonstiges + type: MultiCheckbox + identifier: multicheckbox-4 + label: 'Wie bin ich auf E&P Reisen aufmerksam geworden?' + - + type: LinkedCheckbox + identifier: privacypolicyaccepted + label: 'Ich akzeptiere die %s.' + properties: + pageUid: '355' + linkText: 'Datenschutzbestimmungen' + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty diff --git a/packages/ep_theme/Resources/Private/Forms/ContactForm.form.yaml b/packages/ep_theme/Resources/Private/Forms/ContactForm.form.yaml new file mode 100644 index 0000000..3a0ec95 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Forms/ContactForm.form.yaml @@ -0,0 +1,148 @@ + +renderingOptions: + submitButtonLabel: Absenden +type: Form +identifier: contactform +label: Kontaktformular +prototypeName: contactform +finishers: + - + identifier: BpnApiFinisher + options: + mapping: 'lastName:lastName,firstName:firstName,gender:gender,email:email,street:street,zipCode:zipCode,city:city,phone:phone' + - + options: + subject: 'Kontaktanfrage von {firstName} {lastName}' + recipientAddress: info@ep-reisen.de + recipientName: 'E&P Reisen' + senderAddress: info@ep-reisen.de + senderName: '{firstName} {lastName}' + replyToAddress: '{email}' + carbonCopyAddress: '' + blindCarbonCopyAddress: '' + format: html + attachUploads: false + translation: + language: '' + identifier: EmailToReceiver + - + options: + pageUid: '1387' + additionalParameters: '' + identifier: Redirect +renderables: + - + renderingOptions: + previousButtonLabel: 'Previous step' + nextButtonLabel: 'Neue Seite' + type: Page + identifier: page-1 + label: '' + renderables: + - + defaultValue: '' + type: Text + identifier: firstName + label: Vorname + properties: + fluidAdditionalAttributes: + placeholder: 'Vorname' + required: required + elementDescription: '' + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: lastName + label: 'Nachname' + properties: + fluidAdditionalAttributes: + placeholder: 'Nachname' + required: required + elementDescription: '' + validators: + - + identifier: NotEmpty + - + properties: + options: + M: M + W: W + D: D + fluidAdditionalAttributes: + required: required + type: SingleSelect + identifier: gender + label: 'Gender' + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: email + label: 'E-Mail' + properties: + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty + - + identifier: EmailAddress + - + defaultValue: '' + type: Text + identifier: street + label: 'Straße' + properties: + fluidAdditionalAttributes: + placeholder: 'Straße & Nr.' + elementDescription: '' + - + defaultValue: '' + type: Text + identifier: zipCode + label: 'PLZ' + - + defaultValue: '' + type: Text + identifier: city + label: 'Ort' + - + defaultValue: '' + type: Text + identifier: phone + label: 'Telefon' + - + defaultValue: '' + type: Textarea + identifier: message + label: 'Deine Kontaktanfrage' + - + defaultValue: '' + type: Text + identifier: bookingNumber + label: 'Buchungsnummer (falls vorhanden)' + - + type: LinkedCheckbox + identifier: privacypolicyaccepted + label: 'Ich akzeptiere die %s.' + properties: + pageUid: '355' + linkText: 'Datenschutzbestimmungen' + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty + - + type: Captcha + identifier: captcha + label: Captcha + properties: + fluidAdditionalAttributes: + required: required + autocomplete: 'off' diff --git a/packages/ep_theme/Resources/Private/Language/de.locallang.xlf b/packages/ep_theme/Resources/Private/Language/de.locallang.xlf new file mode 100644 index 0000000..5ecbc1e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Language/de.locallang.xlf @@ -0,0 +1,113 @@ + + + +
+ + + Slider + + + Bezeichnung + + + Slides + + + Slide + + + Headline + + + Subline + + + Bilder + + + Button + + + Link + + + + Details + + + Zum Post + + + Details & Buchen + + + Details + + + Direkt zum Anfrageformular + + + Lineup + + + Name + + + Beschreibung + + + Bild + + + Facebook URL + + + Twitter URL + + + Instagram URL + + + Youtube URL + + + + Datum von + + + Datum bis + + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte gib eine gültige E-Mail Adresse an + + + + Bitte wähle mindestens einen Newsletter + + + Bitte stimme den Datenschutzbestimmungen zu + + + + Nummer einfach als neuen Kontakt abspeichern und los geht es! Wir beraten Dich über WhatsApp! + + + + diff --git a/packages/ep_theme/Resources/Private/Language/locallang.xlf b/packages/ep_theme/Resources/Private/Language/locallang.xlf new file mode 100644 index 0000000..8c0d32c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Language/locallang.xlf @@ -0,0 +1,132 @@ + + + +
+ + + Slider + + + Bezeichnung + + + Slides + + + Slide + + + Headline + + + Subline + + + Bilder + + + Button + + + Link + + + + Details + + + Zum Post + + + Details & Buchen + + + Details + + + Directly to the request form + + + Lineup + + + Name + + + Beschreibung + + + Bild + + + Facebook URL + + + Twitter URL + + + Instagram URL + + + Youtube URL + + + + Datum von + + + Datum bis + + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte auswählen + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Ungültige E-Mail Adresse + + + Ungültige E-Mail Adresse + + + Deine Bestätigung ist erforderlich + + + + Nummer einfach als neuen Kontakt abspeichern und los geht es! Wir beraten Dich über WhatsApp! + + + + diff --git a/packages/ep_theme/Resources/Private/Language/locallang_db.xlf b/packages/ep_theme/Resources/Private/Language/locallang_db.xlf new file mode 100644 index 0000000..a730cd2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Language/locallang_db.xlf @@ -0,0 +1,74 @@ + + + +
+ + + Slider + + + Bezeichnung + + + Slides + + + Slide + + + Headline + + + Subline + + + Bilder + + + Button + + + Link + + + Lineup + + + Name + + + Beschreibung + + + Bild + + + Facebook URL + + + Twitter URL + + + Instagram URL + + + Youtub URL + + + Siegel + + + Titel + + + Untertitel + + + Link + + + Seite + + + + diff --git a/packages/ep_theme/Resources/Private/Language/locallang_mod.xlf b/packages/ep_theme/Resources/Private/Language/locallang_mod.xlf new file mode 100644 index 0000000..ec927ef --- /dev/null +++ b/packages/ep_theme/Resources/Private/Language/locallang_mod.xlf @@ -0,0 +1,17 @@ + + + +
+ + + E&P SEO Übersicht + + + Page title und Meta Description Übersicht + + + Seiten + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Email/SystemEmail.html b/packages/ep_theme/Resources/Private/Layouts/Email/SystemEmail.html new file mode 100644 index 0000000..bd3f29f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Email/SystemEmail.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+ + +
+
+ + + + + + + + + + + +
+ +
+ +
+ + +
+
+ + +
+ + +
+
+ + + + + + + + +
+ + + +
+ + +
+
+ + +
+
+
+ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Bare.html b/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Bare.html new file mode 100644 index 0000000..d2413b0 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Bare.html @@ -0,0 +1 @@ + diff --git a/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Default.html b/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Default.html new file mode 100644 index 0000000..4a12bcd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Default.html @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Form/Default.html b/packages/ep_theme/Resources/Private/Layouts/Form/Default.html new file mode 100644 index 0000000..8027861 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Form/Default.html @@ -0,0 +1 @@ + diff --git a/packages/ep_theme/Resources/Private/Layouts/Gridelements/Default.html b/packages/ep_theme/Resources/Private/Layouts/Gridelements/Default.html new file mode 100644 index 0000000..5852942 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Gridelements/Default.html @@ -0,0 +1,2 @@ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/News/Detail.html b/packages/ep_theme/Resources/Private/Layouts/News/Detail.html new file mode 100644 index 0000000..dbdac17 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/News/Detail.html @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Backend.html b/packages/ep_theme/Resources/Private/Layouts/Page/Backend.html new file mode 100644 index 0000000..a606ca0 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Backend.html @@ -0,0 +1 @@ + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Detail.html b/packages/ep_theme/Resources/Private/Layouts/Page/Detail.html new file mode 100644 index 0000000..6818ea7 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Detail.html @@ -0,0 +1,23 @@ + + + + + + + +
+ + +
+
+ +
+ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Frontpage.html b/packages/ep_theme/Resources/Private/Layouts/Page/Frontpage.html new file mode 100644 index 0000000..093cef2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Frontpage.html @@ -0,0 +1,31 @@ + + + + +
+ +
+
+ + + + + + +
+
+ +
+ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/FrontpageEvent.html b/packages/ep_theme/Resources/Private/Layouts/Page/FrontpageEvent.html new file mode 100644 index 0000000..b95d455 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/FrontpageEvent.html @@ -0,0 +1,22 @@ + + + +
+ + +
+
+ +
+
+ +
+ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Popup.html b/packages/ep_theme/Resources/Private/Layouts/Page/Popup.html new file mode 100644 index 0000000..c0d8ba9 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Popup.html @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Search.html b/packages/ep_theme/Resources/Private/Layouts/Page/Search.html new file mode 100644 index 0000000..2a564bd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Search.html @@ -0,0 +1,18 @@ + + + + +
+ +
+
+ +
+ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Subpage.html b/packages/ep_theme/Resources/Private/Layouts/Page/Subpage.html new file mode 100644 index 0000000..215d855 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Subpage.html @@ -0,0 +1,31 @@ + + + +
+ + + + + + +
+
+ + + +
+
+ +
+ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeft.html b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeft.html new file mode 100644 index 0000000..8e2ca53 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeft.html @@ -0,0 +1,35 @@ + + + +
+ + + + + + +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeftEvent.html b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeftEvent.html new file mode 100644 index 0000000..1574c07 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeftEvent.html @@ -0,0 +1,29 @@ + + + +
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRight.html b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRight.html new file mode 100644 index 0000000..9336a40 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRight.html @@ -0,0 +1,35 @@ + + + +
+ + + + + + +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRightEvent.html b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRightEvent.html new file mode 100644 index 0000000..f3492f2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRightEvent.html @@ -0,0 +1,29 @@ + + + +
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Travelinfo.html b/packages/ep_theme/Resources/Private/Layouts/Page/Travelinfo.html new file mode 100644 index 0000000..b89f0a4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Travelinfo.html @@ -0,0 +1,28 @@ + + +
+ + + + +
Login
+
My E&P
+
+
+
+ +
+
+ +
+ + diff --git a/packages/ep_theme/Resources/Private/Layouts/Product/Default.html b/packages/ep_theme/Resources/Private/Layouts/Product/Default.html new file mode 100644 index 0000000..8027861 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Product/Default.html @@ -0,0 +1 @@ + diff --git a/packages/ep_theme/Resources/Private/Partials/Badge.html b/packages/ep_theme/Resources/Private/Partials/Badge.html new file mode 100644 index 0000000..73bf15e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Badge.html @@ -0,0 +1,29 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Chatbot.html b/packages/ep_theme/Resources/Private/Partials/Chatbot.html new file mode 100644 index 0000000..c8f5d62 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Chatbot.html @@ -0,0 +1,58 @@ + + +
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/City/Details.html b/packages/ep_theme/Resources/Private/Partials/City/Details.html new file mode 100644 index 0000000..d6a79a6 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/City/Details.html @@ -0,0 +1,154 @@ +{namespace ep=EP\EpTheme\ViewHelpers} +
+
+ + +
+ {city.name} +
+
+ +

+ Ort +

+
+
+
+ {city.description -> f:format.html()} +
+
+ +
+ +
+ + +
+ + + +
+
+ +
+ + +
+
+ + + +
+
+
+
+ + + +
+ + Alle anzeigen + +
+
+
+
+
+
+ +
+ {city.descriptionExtended -> f:format.html()} +
+
+ + + + +
+ + +
+ Party +
+
+ +

+ Party +

+
+
+ {city.party -> f:format.html()} +
+
+ +
+ + +
+ Freizeitangebot +
+
+ +

+ Freizeitangebot +

+
+
+ {city.leisure -> f:format.html()} +
+
+
diff --git a/packages/ep_theme/Resources/Private/Partials/City/Teaser.html b/packages/ep_theme/Resources/Private/Partials/City/Teaser.html new file mode 100644 index 0000000..731961b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/City/Teaser.html @@ -0,0 +1,52 @@ + + + +
+
+ {ep:icon(icon: 'flag-{city.country.code -> f:format.case(mode: \'lower\')}', class: 'w-8 h-6 absolute top-0 left-0')} + + + + + + + + + + + + + + + + + + +
+
+ + {city.name} + + +
+ {city.teaser -> f:format.crop(maxCharacters: settings.teaserTextMaxChars) -> f:format.html()} +
+
+ {f:translate(key: 'tx_eptheme.label.teaserbutton.default', extensionName: 'ep_theme')} +
+
+
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Concept/Teaser.html b/packages/ep_theme/Resources/Private/Partials/Concept/Teaser.html new file mode 100644 index 0000000..5396ecd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Concept/Teaser.html @@ -0,0 +1,51 @@ + + + + +
+
+ + + + + + + + + + + + + + + + + + +
+ + {concept.name} + + +
+
+
+ {concept.teaser -> f:format.crop(maxCharacters: settings.teaserTextMaxChars) -> f:format.html()} +
+
+
+
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Config.html b/packages/ep_theme/Resources/Private/Partials/Config.html new file mode 100644 index 0000000..a4b0069 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Config.html @@ -0,0 +1,10 @@ + + diff --git a/packages/ep_theme/Resources/Private/Partials/ContactBar.html b/packages/ep_theme/Resources/Private/Partials/ContactBar.html new file mode 100644 index 0000000..7976c6a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/ContactBar.html @@ -0,0 +1,111 @@ + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/ContactForm/Error.html b/packages/ep_theme/Resources/Private/Partials/ContactForm/Error.html new file mode 100644 index 0000000..be31d82 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/ContactForm/Error.html @@ -0,0 +1,18 @@ + + +
+

+ Es ist ein Problem aufgetreten. Bitte versucht es noch einmal. +

+

+ +

+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/ContactForm/Success.html b/packages/ep_theme/Resources/Private/Partials/ContactForm/Success.html new file mode 100644 index 0000000..570d878 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/ContactForm/Success.html @@ -0,0 +1,19 @@ + + +
+

+ Vielen Dank für Eure Anfrage. Wir werden uns schnellstmöglich mit Euch in Verbindung + setzen. +

+

+ +

+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Disrupter/Large.html b/packages/ep_theme/Resources/Private/Partials/Disrupter/Large.html new file mode 100644 index 0000000..38230a7 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Disrupter/Large.html @@ -0,0 +1,63 @@ + + +
+ + + +
+ +
+ {data.subheadline} + {data.headline} +
+ {data.bodytext -> f:format.html()} +
+ +
+ + + {button.container.label} + + +
+
+
+
+ + + +
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Disrupter/LastChance.html b/packages/ep_theme/Resources/Private/Partials/Disrupter/LastChance.html new file mode 100644 index 0000000..a76eb08 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Disrupter/LastChance.html @@ -0,0 +1,29 @@ + + +
+ +
+
+ {data.headline} +
+
+ {data.text -> f:format.html()} +
+
+ {data.flexForm.buttons -> v:iterator.first() -> v:variable.set(name: 'button')} + + {button.container.label -> f:format.raw()} + +
+
+ +
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Disrupter/Outlines.html b/packages/ep_theme/Resources/Private/Partials/Disrupter/Outlines.html new file mode 100644 index 0000000..d5ad0b5 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Disrupter/Outlines.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Disrupter/Reminder.html b/packages/ep_theme/Resources/Private/Partials/Disrupter/Reminder.html new file mode 100644 index 0000000..5974779 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Disrupter/Reminder.html @@ -0,0 +1,30 @@ + + + +
+
+ {data.headline} +
+ +
+ {data.text -> f:format.html()} +
+ + {button.container.label -> f:format.raw()} + +
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Disrupter/Small.html b/packages/ep_theme/Resources/Private/Partials/Disrupter/Small.html new file mode 100644 index 0000000..36925d7 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Disrupter/Small.html @@ -0,0 +1,42 @@ + + +
+ +
+
+
+ + + +
+
+ {data.headline} + {data.text -> f:format.html()} +
+ + + + {button.container.label} + + + +
+
+
+
+ +
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Disturber/Large.html b/packages/ep_theme/Resources/Private/Partials/Disturber/Large.html new file mode 100644 index 0000000..fa2f2ca --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Disturber/Large.html @@ -0,0 +1,64 @@ + + +
+ + + +
+ +
+ {data.subheader} + {data.header} +
+ {data.bodytext -> f:format.html()} +
+ +
+ + + {button.container.label} + + +
+
+
+
+ + + +
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Disturber/LastChance.html b/packages/ep_theme/Resources/Private/Partials/Disturber/LastChance.html new file mode 100644 index 0000000..c19c492 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Disturber/LastChance.html @@ -0,0 +1,21 @@ + + +
+
+ {data.header} +
+
+ {data.bodytext -> f:format.html()} +
+
+ {data.flexForm.buttons -> v:iterator.first() -> v:variable.set(name: 'button')} + + {button.container.label -> f:format.raw()} + +
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Disturber/Reminder.html b/packages/ep_theme/Resources/Private/Partials/Disturber/Reminder.html new file mode 100644 index 0000000..e542856 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Disturber/Reminder.html @@ -0,0 +1,32 @@ + + + +
+
+ {data.header} +
+ +
+
+ {data.bodytext -> f:format.html()} +
+
+ + {button.container.label -> f:format.raw()} + +
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Disturber/Small.html b/packages/ep_theme/Resources/Private/Partials/Disturber/Small.html new file mode 100644 index 0000000..4c3a560 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Disturber/Small.html @@ -0,0 +1,42 @@ + + +
+ +
+
+
+ + + +
+
+ {data.header} + {data.bodytext -> f:format.html()} +
+ + + + {button.container.label} + + + +
+
+
+
+ +
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/EventDisturber.html b/packages/ep_theme/Resources/Private/Partials/EventDisturber.html new file mode 100644 index 0000000..021da35 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/EventDisturber.html @@ -0,0 +1,34 @@ + + +
+
+
+
+
+ {headline -> f:format.raw()} +
+
+
    + +
  • + {ep:icon(icon: 'check', class: 'w-6 h-6')} + {listItem -> f:format.raw()} +
  • +
    +
+
+ +
+
+
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/EventHero.html b/packages/ep_theme/Resources/Private/Partials/EventHero.html new file mode 100644 index 0000000..c5cc2ee --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/EventHero.html @@ -0,0 +1,75 @@ + + +
+
+
+
+ +
+ {settings.eventHeroClaim -> f:format.raw()} +
+
+ {settings.eventHeroDescription -> f:format.raw()} +
+
+
+
+ {settings.eventDateDayFrom -> f:format.raw()} - {settings.eventDateDayTo -> f:format.raw()} {settings.eventDateMonth -> f:format.raw()} + +
+
+ + + + + + + + + + + + + +
+ + + + +
+ {badgeData.title -> f:format.raw()} + {badgeData.subtitle -> f:format.raw()} +
+
+
+
+
+ +
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Facts.html b/packages/ep_theme/Resources/Private/Partials/Facts.html new file mode 100644 index 0000000..6df03a5 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Facts.html @@ -0,0 +1,29 @@ + + + +
+

+ + + {title} + + + Facts + + +

+
    + +
  • + {ep:icon(icon: 'check', class: 'w-6 h-6 text-ep-primary-light')} + {fact.name} +
  • +
    +
+
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Faq.html b/packages/ep_theme/Resources/Private/Partials/Faq.html new file mode 100644 index 0000000..d589293 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Faq.html @@ -0,0 +1,62 @@ + + + +
+
+
+ + Häufig gestellte Fragen + + +
+ + {ep:icon(icon: 'search', class: 'w-6 h-6 text-white')} +
+
+ +
+ + +
+
+ + +
+ {ep:icon(icon: 'info-circle', class: 'w-4 h-4')} + + Weitere Reiseinfos findet ihr hier + +
+
+
+
+
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/FaqGroup.html b/packages/ep_theme/Resources/Private/Partials/FaqGroup.html new file mode 100644 index 0000000..ca91b20 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/FaqGroup.html @@ -0,0 +1,40 @@ + + + +
+ +
+
+ +
+ {category} +
+
+ +
+ + +
+
+
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Footer.html b/packages/ep_theme/Resources/Private/Partials/Footer.html new file mode 100644 index 0000000..14e2293 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Footer.html @@ -0,0 +1,191 @@ + + +
+ + + +
+
+ + +
+ + {menuheader.title} + + +
    + +
  • + + {submenuitem.title} + +
  • +
    +
+
+
+
+
+
+ +
+ {totalRatings.totalAverage -> f:format.number(decimalSeparator: ',')} Sterne bei {totalRatings.totalCount} Bewertungen +
+ + +
+
+ + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Captcha.html b/packages/ep_theme/Resources/Private/Partials/Form/Captcha.html new file mode 100644 index 0000000..e977f46 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Captcha.html @@ -0,0 +1,38 @@ + + + + +
+ + + The "now" param keeps bw_captcha 3.x from sending a Content-Length that is the base64 size of the + binary image (too large), which makes Chrome drop the response. Fixed upstream in bw_captcha 4.0.1. + + Captcha image + + + +
+
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Checkbox.html b/packages/ep_theme/Resources/Private/Partials/Form/Checkbox.html new file mode 100644 index 0000000..2fc546d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Checkbox.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/ContentElement.html b/packages/ep_theme/Resources/Private/Partials/Form/ContentElement.html new file mode 100644 index 0000000..f38f99a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/ContentElement.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + {element.properties.contentElementUid} + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Date.html b/packages/ep_theme/Resources/Private/Partials/Form/Date.html new file mode 100644 index 0000000..834ca46 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Date.html @@ -0,0 +1,14 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Email.html b/packages/ep_theme/Resources/Private/Partials/Form/Email.html new file mode 100644 index 0000000..590dbdb --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Email.html @@ -0,0 +1,14 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Field/Field.html b/packages/ep_theme/Resources/Private/Partials/Form/Field/Field.html new file mode 100644 index 0000000..60fb3b4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Field/Field.html @@ -0,0 +1,18 @@ + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Fieldset.html b/packages/ep_theme/Resources/Private/Partials/Form/Fieldset.html new file mode 100644 index 0000000..7e99729 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Fieldset.html @@ -0,0 +1,12 @@ + + +
+ + {formvh:translateElementProperty(element: element, property: 'label')} + + + + +
+
+ diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Form/Navigation.html b/packages/ep_theme/Resources/Private/Partials/Form/Form/Navigation.html new file mode 100644 index 0000000..4b8ec61 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Form/Navigation.html @@ -0,0 +1,28 @@ + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/LinkedCheckbox.html b/packages/ep_theme/Resources/Private/Partials/Form/LinkedCheckbox.html new file mode 100644 index 0000000..3d52470 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/LinkedCheckbox.html @@ -0,0 +1,19 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/MultiCheckbox.html b/packages/ep_theme/Resources/Private/Partials/Form/MultiCheckbox.html new file mode 100644 index 0000000..fde92f4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/MultiCheckbox.html @@ -0,0 +1,35 @@ + + + + {formvh:translateElementProperty(element: element, property: 'label')} + + + + + + + + + + + +
+ +
+
+
+
+ diff --git a/packages/ep_theme/Resources/Private/Partials/Form/MultiSelect.html b/packages/ep_theme/Resources/Private/Partials/Form/MultiSelect.html new file mode 100644 index 0000000..4d1440d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/MultiSelect.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Number.html b/packages/ep_theme/Resources/Private/Partials/Form/Number.html new file mode 100644 index 0000000..f323739 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Number.html @@ -0,0 +1,14 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/RadioButton.html b/packages/ep_theme/Resources/Private/Partials/Form/RadioButton.html new file mode 100644 index 0000000..9e2064a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/RadioButton.html @@ -0,0 +1,22 @@ + + + + {formvh:translateElementProperty(element: element, property: 'label')} + +
+ +
+
+
+
+ diff --git a/packages/ep_theme/Resources/Private/Partials/Form/SingleSelect.html b/packages/ep_theme/Resources/Private/Partials/Form/SingleSelect.html new file mode 100644 index 0000000..4e1da4a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/SingleSelect.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/StaticText.html b/packages/ep_theme/Resources/Private/Partials/Form/StaticText.html new file mode 100644 index 0000000..74f182d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/StaticText.html @@ -0,0 +1,11 @@ + + + + + + {formvh:translateElementProperty(element: element, property: 'text') -> f:format.nl2br()} + +

+
+
+ diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Telephone.html b/packages/ep_theme/Resources/Private/Partials/Form/Telephone.html new file mode 100644 index 0000000..aed976c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Telephone.html @@ -0,0 +1,14 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Text.html b/packages/ep_theme/Resources/Private/Partials/Form/Text.html new file mode 100644 index 0000000..9d78acd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Text.html @@ -0,0 +1,13 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Form/Textarea.html b/packages/ep_theme/Resources/Private/Partials/Form/Textarea.html new file mode 100644 index 0000000..b6e522d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Form/Textarea.html @@ -0,0 +1,15 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Header/Header.html b/packages/ep_theme/Resources/Private/Partials/Header/Header.html new file mode 100644 index 0000000..da75183 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Header/Header.html @@ -0,0 +1,67 @@ + + + + + +

+ {header -> f:format.raw()} +

+
+ +

+ {header -> f:format.raw()} +

+
+ +

+ {header -> f:format.raw()} +

+
+ +

+ {header -> f:format.raw()} +

+
+ +
+ {header -> f:format.raw()} +
+
+ +

+ {header -> f:format.raw()} +

+
+ +

+ {header -> f:format.raw()} +

+
+ +

+ {subheader -> f:format.raw()} {header -> f:format.raw()} +

+
+ +

+ {subheader -> f:format.raw()} {header -> f:format.raw()} +

+
+ + + + + + + + > +
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Header/Standalone.html b/packages/ep_theme/Resources/Private/Partials/Header/Standalone.html new file mode 100644 index 0000000..be5a449 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Header/Standalone.html @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/HeaderSlider.html b/packages/ep_theme/Resources/Private/Partials/HeaderSlider.html new file mode 100644 index 0000000..5097109 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/HeaderSlider.html @@ -0,0 +1,87 @@ + + +
+
+
    + +
  • +
    + + +
    + + + + {image.alternative} + +
    +
    +
    +
    +
    +
    + + {slide.headline} + + + + + + + {slide.button} + + +
    +
    +
    + +
    + + +
    +
    Ski-
    Pass
    +
    inklusive
    +
    +
    +
    + + + +
    + + + + +
    + {badgeData.title -> f:format.raw()} + + {badgeData.subtitle -> f:format.raw()} + +
    +
    +
    +
    +
    +
    +
    +
    +
  • +
    +
+
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/HeaderSliderSmall.html b/packages/ep_theme/Resources/Private/Partials/HeaderSliderSmall.html new file mode 100644 index 0000000..a1b96f3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/HeaderSliderSmall.html @@ -0,0 +1,79 @@ + + + +
+ + + + + + + + + +
+
+
+
+ {slide.headline} +
+
+ {slide.subline} +
+ + + {slide.button} + + +
+
+
+ +
+ + +
+

Ski-
Pass

+

inklusive

+
+
+
+ + + + + + + +
+
+ +
+ {slide.image.0.description -> f:format.htmlspecialchars()} +
+
+
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Hotel/Details.html b/packages/ep_theme/Resources/Private/Partials/Hotel/Details.html new file mode 100644 index 0000000..45cba2f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Hotel/Details.html @@ -0,0 +1,204 @@ +{namespace ep=EP\EpTheme\ViewHelpers} +{hotel.name -> f:format.raw()}{f:if(condition: hotel.categoryLabel, then: ' ({hotel.categoryLabel -> f:format.raw()})')} +
+
+ + +
+ {hotelTitle} +
+
+ +

+ {hotelTitle} +

+
+
+ +
    + +
  • + {ep:icon(icon: item.icon, class: 'w-5 h-5 md:w-6 md:h-6 shrink-0 text-ep-primary')} + {item.label} +
  • +
    +
+
+ {hotel.description -> f:format.html()} +
+ + + +
+ + +
+ + + +
+
+ +
+ + +
+
+ + + +
+
+
+ + + +
+ + Alle anzeigen + +
+
+
+
+
+
+ + +
+

Gerade noch kein Angebot buchbar? Dann frag doch schon mal an: hier geht es zum Kontaktformular.

+
+
+
+
+ + + +
+ + +
+ Ausstattung +
+
+ +

+ Ausstattung +

+
+
+ {hotel.features -> f:format.html()} +
+ +
+ + + +
+ Apartments +
+
+ +

Apartments

+
+
+
+ + + +
+ Ferienwohnungen +
+
+ +

+ Ferienwohnungen +

+
+
+
+ + + +
+ Zimmerarten +
+
+ +

+ Zimmerarten +

+
+
+
+ {hotel.roomTypes -> f:format.html()} +
+
+ +
+ + +
+ Zusatzinformationen +
+
+ +

+ Zusatzinformationen +

+
+
+ {hotel.additionalInformation -> f:format.html()} +
+
+
diff --git a/packages/ep_theme/Resources/Private/Partials/Hotel/Teaser.html b/packages/ep_theme/Resources/Private/Partials/Hotel/Teaser.html new file mode 100644 index 0000000..c40a3ed --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Hotel/Teaser.html @@ -0,0 +1,53 @@ + + + +
+
+ {ep:icon(icon: 'flag-{hotel.country.code -> f:format.case(mode: \'lower\')}', class: 'w-8 h-6 absolute top-0 left-0')} + + + + + + + + +
+
+ + {hotel.name -> f:format.nl2br()} + + + +
+ Kategorie {hotel.category} +
+
+
+ {hotel.teaser -> f:format.crop(maxCharacters: settings.teaserTextMaxChars) -> f:format.html()} + + + ab {hotel.minPrice -> ep:price()} €aktuell nicht buchbar + + +
+
+ {f:translate(key: 'tx_eptheme.label.teaserbutton.default', extensionName: 'ep_theme')} +
+
+
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/ImageGallery.html b/packages/ep_theme/Resources/Private/Partials/ImageGallery.html new file mode 100644 index 0000000..cb8987c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/ImageGallery.html @@ -0,0 +1,64 @@ + + +
+
+ {ep:icon(icon: 'search', class: 'w-8 h-8 md:w-10 md:h-10')} + + {images -> f:count()} + +
+ + + + + + + + + + + +
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Journey/Details.html b/packages/ep_theme/Resources/Private/Partials/Journey/Details.html new file mode 100644 index 0000000..cfe2052 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Journey/Details.html @@ -0,0 +1,33 @@ + + +
+ +

+ Busanreise +

+
+ {journey.byBus -> f:format.html()} +
+
+ + + + +

+ Eigenanreise +

+ {journey.byCar -> f:format.html()} + + + +
+
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/Mainnav.html b/packages/ep_theme/Resources/Private/Partials/Mainnav.html new file mode 100644 index 0000000..52cfadd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Mainnav.html @@ -0,0 +1,168 @@ + + + +
+
+ + + + +
+ + + + + + +
+
+
+
+ + + + + + + + + + + + + +
  • + + {subsubitem.title} + +
  • +
    + +
  • + + {subsubitem.title} + +
  • +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Map/Osm.html b/packages/ep_theme/Resources/Private/Partials/Map/Osm.html new file mode 100644 index 0000000..6343836 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Map/Osm.html @@ -0,0 +1,10 @@ +
    data-osm-zoom-value="{zoom}" + data-osm-lat-value="{lat}" data-osm-lng-value="{lng}" + data-osm-external-link-value="{externalLink}" + data-osm-markers-value='{markers -> f:format.raw()}' +>
    diff --git a/packages/ep_theme/Resources/Private/Partials/Mobilenav.html b/packages/ep_theme/Resources/Private/Partials/Mobilenav.html new file mode 100644 index 0000000..7c00a5d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Mobilenav.html @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/News/Detail/MediaContainer.html b/packages/ep_theme/Resources/Private/Partials/News/Detail/MediaContainer.html new file mode 100644 index 0000000..100fb74 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/News/Detail/MediaContainer.html @@ -0,0 +1,51 @@ + + + + + + + + {images.0.alternative} + + + +
    + + + + +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/News/List/Item.html b/packages/ep_theme/Resources/Private/Partials/News/List/Item.html new file mode 100644 index 0000000..85a3764 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/News/List/Item.html @@ -0,0 +1,70 @@ + + +
    + +
    +
    + + + + + {mediaElement.originalResource.alternative} + + + + + + + {mediaElement.originalResource.alternative} + + + + +
    +
    + +

    + {newsItem.title} +

    + +
    +

    + {newsItem.author} am + + in + + + {tag.title} + + {f:if(condition: iteration.isLast, else: ', ')} + + +

    + +
    + + + {newsItem.teaser -> f:format.crop(maxCharacters: '{settings.cropMaxCharacters}', respectWordBoundaries:'1') -> f:format.html()} + + + {newsItem.bodytext -> f:format.crop(maxCharacters: '{settings.cropMaxCharacters}', respectWordBoundaries:'1') -> f:format.html()} + + +
    +
    +
    + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/NotBookable.html b/packages/ep_theme/Resources/Private/Partials/NotBookable.html new file mode 100644 index 0000000..482398c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/NotBookable.html @@ -0,0 +1,11 @@ +{namespace v=FluidTYPO3\Vhs\ViewHelpers} +
    +

    Die von Dir gewählte Unterkunft ist aktuell nicht buchbar.

    +
      +
    • Wir informieren dich, wenn deine Reise buchbar ist - Trage dich in unsere + Reisealert-Liste ein!
    • +
    • Du kannst diese aber ggf. auch vorreservieren, oder wir helfen dir, eine andere Unterkunft + zu finden. Ruf uns an unter {settings.phoneNumber} + oder schreibe an .
    • +
    +
    diff --git a/packages/ep_theme/Resources/Private/Partials/Officetip.html b/packages/ep_theme/Resources/Private/Partials/Officetip.html new file mode 100644 index 0000000..0a8a223 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Officetip.html @@ -0,0 +1,26 @@ + +
    +
    +
    +

    + Officetipp +

    +
    +
    + + + +
    +
    + + {officetip.text -> f:format.nl2br()} + + {officetip.author.name} +
    +
    +
    +
    diff --git a/packages/ep_theme/Resources/Private/Partials/Pricetable.html b/packages/ep_theme/Resources/Private/Partials/Pricetable.html new file mode 100644 index 0000000..15b689a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Pricetable.html @@ -0,0 +1,166 @@ + + + + +
    + {nonBookableInfo.text -> f:format.html()} +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Zimmertyp + + Preis/Pers. + + inkl. +
    + {row.roomName} +
    + + + + Jetzt buchen + + + + ausgebucht + + +
    +
    + + + {row.minPrice -> ep:price()} € + + + {row.minPrice -> ep:price()} € + + + + + + {ep:icon(icon: 'bus', class: 'w-4 h-4 md:w-5 md:h-5')} + + + + + {ep:icon(icon: 'tag', class: 'w-4 h-4 md:w-5 md:h-5')} + + + + + + +
    +
    +
    +
    + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Product/Details.html b/packages/ep_theme/Resources/Private/Partials/Product/Details.html new file mode 100644 index 0000000..5632198 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Product/Details.html @@ -0,0 +1,83 @@ +
    +
    +

    + Konzept +

    + {product.conceptDescription -> f:format.html()} + + + Zum Anfrageformular + + +
    +
    +

    + Verpflegung +

    + {product.boardDescription -> f:format.html()} +
    + +
    +

    + Inklusivleistungen +

    + {product.includedServices -> f:format.html()} + +
    + + + + +
    + + ab
    {product.minPrice} € +
    +
    +
    +
    +
    +
    + +
    +

    + Zusatzleistungen +

    + {product.additionalServices -> f:format.html()} + +
    +
    + +
    +

    + Programm +

    + {product.programmeDescription -> f:format.html()} +
    +
    + +
    + + +

    + {product.skiPassAltTitle} +

    +
    + +

    + Skipass, Kurse, Leihmaterial +

    +
    +
    + {product.skiPassDescription -> f:format.html()} +
    +
    +
    diff --git a/packages/ep_theme/Resources/Private/Partials/Product/HeaderDetail.html b/packages/ep_theme/Resources/Private/Partials/Product/HeaderDetail.html new file mode 100644 index 0000000..fcb7913 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Product/HeaderDetail.html @@ -0,0 +1,88 @@ + + + + + + + + + + + +
    + + + + + +
    +
    +
    + + {product.name}{f:if(condition: product.subline, then: ' {product.subline}')} + + + {product.region.name} + +
    + {ep:icon(icon: 'flag-{product.country.code -> f:format.case(mode: \'lower\')}', class: 'w-8 h-6 md:w-10 md:h-8')} + + + + {ep:icon(icon: 'location-marker', class: 'w-4 h-4 md:w-6 md:h-6 text-white')} + + + + + {ep:icon(icon: 'location-marker', class: 'w-4 h-4 md:w-6 md:h-6 text-white')} + + + + +
    + {product.concept.name} +
    +
    + +
    + NEU! +
    +
    +
    + + + {ep:icon(icon: 'chevron-left', class: 'w-4 h-4 text-white')} + {backlinkLabel} + + +
    +
    + +
    + +
    + +

    + Ski-
    Pass +

    +

    + inklusive +

    +
    +
    +
    + +
    + {slide.image.0.description -> f:format.htmlspecialchars()} +
    +
    +
    +
    + diff --git a/packages/ep_theme/Resources/Private/Partials/Product/Pickups.html b/packages/ep_theme/Resources/Private/Partials/Product/Pickups.html new file mode 100644 index 0000000..a2d883e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Product/Pickups.html @@ -0,0 +1,81 @@ + + +
    +

    + Bus-Zustiege +

    + + + + + +
    +
    + +
    + +
    +
    +
    +
    +
    +

    + Abfahrtszeiten und -orte unter Vorbehalt. Die genauen Zeiten werden euch kurz vor der Anreise mitgeteilt. +

    +
    + + + + + + + + + + + + + + + +
    + Zustieg + + Abfahrt + + Zuschlag +
    + {pickup.city}
    {pickup.street} +
    + + {pickup.time} Uhr + - + + + + {pickup.price -> f:format.currency(currencySign: '€', decimalSeparator: ',', thousandsSeparator: '.')} + - + +
    +
    + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Product/PriceCalendar.html b/packages/ep_theme/Resources/Private/Partials/Product/PriceCalendar.html new file mode 100644 index 0000000..d9add91 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Product/PriceCalendar.html @@ -0,0 +1,42 @@ + + +
    +
    + + Preiskalender wird geladen … +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Product/Teaser.html b/packages/ep_theme/Resources/Private/Partials/Product/Teaser.html new file mode 100644 index 0000000..e54e2dd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Product/Teaser.html @@ -0,0 +1,136 @@ + + + + +
    + + +
    +
    + + +
    + + + + + + + + +
    +
    + + {ep:icon(class: 'product-teaser__flag', icon: 'flag-{country.code -> f:format.case(mode: \'lower\')}')} + + {concept.name} +
    + + + + + +
    + +
    + NEU! +
    +
    + +
    + {hotel.name} + {hotel.category} +
    +
    +
    +
    + + + {f:variable(name: 'including', value: '{f:render(section: \'Including\', arguments: \'{_all}\') -> v:format.trim()}')} +
    + + hx-get="{ep:uri.ajax(action: 'concept', controller: 'AjaxTeaserPopup', arguments: '{concept: concept.uid, targetUrl: targetUrl}', format: 'html', pageUid: settings.defaultAjaxUid)}" + hx-target="body" + hx-swap="beforeend" + + > + + +
    + {region.name} +
    +
    + {product.name} +
    +
    + +
    + {product.name} +
    +
    + {region.name} +
    +
    +
    + +
    +
    + {ep:nightsOptions(options: nights, daytrip: daytrip)}{f:if(condition: including, then: ' inkl.')} +
    +
      + {including -> f:format.raw()} +
    +
    +
    + ab {minPrice -> ep:price()} € +
    +
    + p. P.{f:if(condition: '{season} == "w" && {skiPassIncluded}', then: ' / inkl. Skipass')} +
    +
    +
    +
    + + + +
  • + {ep:icon(class: 'w-6 h-6 text-ep-primary-light', icon: 'check')} + Skipass +
  • +
    + +
  • + {ep:icon(class: 'w-6 h-6 text-ep-primary-light', icon: 'check')} + {product.feature} +
  • +
    + +
  • + {ep:icon(class: 'w-6 h-6 text-ep-primary-light', icon: 'check')} + Verpflegung +
  • +
    +
    + diff --git a/packages/ep_theme/Resources/Private/Partials/Rating.html b/packages/ep_theme/Resources/Private/Partials/Rating.html new file mode 100644 index 0000000..f527085 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Rating.html @@ -0,0 +1,17 @@ + + +

    + {label} +

    + +
    +
    + +
    +

    + Kundenstimmen +

    + {product.testimonials -> f:format.html()} +
    +
    diff --git a/packages/ep_theme/Resources/Private/Partials/Region/Details.html b/packages/ep_theme/Resources/Private/Partials/Region/Details.html new file mode 100644 index 0000000..cf17499 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Region/Details.html @@ -0,0 +1,196 @@ +{namespace ep=EP\EpTheme\ViewHelpers} +
    +
    + + +
    + {region.name -> f:format.raw()} +
    +
    + +

    + + + Urlaub im Gebiet {region.name -> f:format.raw()} + + + Skiurlaub im Skigebiet {region.name -> f:format.raw()} + + +

    +
    +
    + +
    + + + + + +
    +
    +
    + {region.skiArea -> f:format.html()} +
    + + + +
    + + +
    + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    +
    + + + +
    + + Alle anzeigen + +
    +
    +
    +
    +
    +
    + + + + + +
    + +
    + {region.skiAreaExtended -> f:format.html()} +
    +
    +
    + + + + + +
    + + +
    + {f:if(condition: '{region.season} == "w"', then: 'Skipass', else: 'Highlights')} {region.name} +
    +
    + +

    + {f:if(condition: '{region.season} == "w"', then: 'Skipass', else: 'Highlights')} {region.name} +

    +
    +
    + + {region.skiPass -> f:format.html()} + + + {region.description -> f:format.html()} + +
    + +
    + + +
    + Aktuelles {region.name} +
    +
    + +

    + Aktuelles {region.name} +

    +
    +
    + {region.news -> f:format.html()} +
    +
    +
    diff --git a/packages/ep_theme/Resources/Private/Partials/Region/Teaser.html b/packages/ep_theme/Resources/Private/Partials/Region/Teaser.html new file mode 100644 index 0000000..eb3484c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Region/Teaser.html @@ -0,0 +1,52 @@ + + + +
    +
    + {ep:icon(icon: 'flag-{region.country.code -> f:format.case(mode: \'lower\')}', class: 'w-8 h-6 absolute top-0 left-0')} + + + + + + + + + + + + + + + + + + +
    +
    + + {region.name} + + +
    + {region.teaser -> f:format.crop(maxCharacters: settings.teaserTextMaxChars) -> f:format.html()} +
    +
    + {f:translate(key: 'tx_eptheme.label.teaserbutton.default', extensionName: 'ep_theme')} +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Region/Webcam.html b/packages/ep_theme/Resources/Private/Partials/Region/Webcam.html new file mode 100644 index 0000000..4ba18aa --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Region/Webcam.html @@ -0,0 +1,24 @@ + + + +
    +

    + Webcam +

    + + + +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/RegionAward.html b/packages/ep_theme/Resources/Private/Partials/RegionAward.html new file mode 100644 index 0000000..abdf47f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/RegionAward.html @@ -0,0 +1,20 @@ + + + +
    + + {region.awardLabel -> v:or(alternative: 'Auszeichnung')} + + + + +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/RegionMaps.html b/packages/ep_theme/Resources/Private/Partials/RegionMaps.html new file mode 100644 index 0000000..b26b4d3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/RegionMaps.html @@ -0,0 +1,30 @@ + + + +
    +

    + Pistenplan +

    +
    + + + + + +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Regioninfo.html b/packages/ep_theme/Resources/Private/Partials/Regioninfo.html new file mode 100644 index 0000000..02cde92 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Regioninfo.html @@ -0,0 +1,38 @@ + + +
    +

    + Gebietsinfo +

    +
      + +
    • + {ep:icon(icon: 'ski', class: 'w-4 h-4')} + + Pisten: {region.length} + +
    • +
      + +
    • + {ep:icon(icon: 'mountain', class: 'w-4 h-4')} + + Höhe: {region.height} + +
    • +
      + +
    • + {ep:icon(icon: 'lift', class: 'w-4 h-4')} + + Lifte: {region.lifts} + +
    • +
      +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Search/FilterPanel.html b/packages/ep_theme/Resources/Private/Partials/Search/FilterPanel.html new file mode 100644 index 0000000..99e1d3a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Search/FilterPanel.html @@ -0,0 +1,348 @@ + + +
    + +
    + + +
    +
    +
    +
    + + {ep:icon(icon: 'calendar', class: 'w-6 h-6 text-white absolute top-0 right-0 translate-y-1/2 mr-2')} +
    + +
    + + + +
    +
    + +
    +
    +
    +
    +
    + +
    + +
    +
      + +
    • + +
    • +
      +
    +
    +
    + + + + + +
    + +
    + +
    +
      + +
    • + + +
        + +
      • + +
      • +
        +
      +
      +
    • +
      +
    +
    +
    + +
    + +
    +
      + +
    • + +
    • +
      +
    +
    +
    + +
    + +
    + +
    +
    + + +
    + +
    +
      + + +
    • + +
    • +
      +
      +
    +
    +
    + +
    + +
    +
      + + +
    • + +
    • +
      +
      +
    +
    +
    + + +
    + +
    +
      + + +
    • + +
    • +
      +
      +
    +
    +
    + + +
    + +
    +
      +
    • + +
    • +
    +
    +
    +
    + +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Search/Main.html b/packages/ep_theme/Resources/Private/Partials/Search/Main.html new file mode 100644 index 0000000..01ce3c3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Search/Main.html @@ -0,0 +1,35 @@ + + +
    +
    + +
    +
    +

    + Suchergebnis +

    + +
    + +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Search/MobileSearch.html b/packages/ep_theme/Resources/Private/Partials/Search/MobileSearch.html new file mode 100644 index 0000000..193b9fa --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Search/MobileSearch.html @@ -0,0 +1,34 @@ + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Search/Result.html b/packages/ep_theme/Resources/Private/Partials/Search/Result.html new file mode 100644 index 0000000..348b16a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Search/Result.html @@ -0,0 +1,114 @@ + + +
    +
    + + + {ep:icon(icon: 'emoji-happy', class: 'w-6 h-6 text-white')} + + + {ep:icon(icon: 'emoji-sad', class: 'w-6 h-6 text-white')} + + +
    +
    + Deine Suche + + + + nach {meta.destinationName} + + + {meta.dateRange} + + ergab {meta.total} Treffer + + + ergab leider keine genauen Treffer. Bitte benutze die Filter. + + +
    +
    + + +
    +

    + {section.concept.name} +

    +
    +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Snowreport.html b/packages/ep_theme/Resources/Private/Partials/Snowreport.html new file mode 100644 index 0000000..fed5a37 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Snowreport.html @@ -0,0 +1,12 @@ + +
    +

    + Schneehöhe aktuell +

    +
      +
    • Tal: {snowreport.snowLevelValley}cm
    • +
    • Berg: {snowreport.snowLevelMountain}cm
    • +
    +

    Letzter Schneefall am {snowreport.lastSnowFall -> f:format.date(format: 'd.m.Y')}

    +
    +
    diff --git a/packages/ep_theme/Resources/Private/Partials/SubpageHeader.html b/packages/ep_theme/Resources/Private/Partials/SubpageHeader.html new file mode 100644 index 0000000..d90d4c4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/SubpageHeader.html @@ -0,0 +1,68 @@ + + +
    + + + + + + + + + +
    +
    +
    + + {topline} + + + {headline} + +
    +
    +
    + +
    {headerImage.description -> f:format.htmlspecialchars()}
    +
    + +
    + + +
    +

    Ski-
    Pass

    +

    inklusive

    +
    +
    +
    + + + +
    + + + + +
    + {badgeData.title -> f:format.raw()} + + {badgeData.subtitle -> f:format.raw()} + +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Teamer.html b/packages/ep_theme/Resources/Private/Partials/Teamer.html new file mode 100644 index 0000000..f09bcc8 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Teamer.html @@ -0,0 +1,23 @@ + + + + {teamer -> v:iterator.first() -> v:variable.set(name: 'teamerInCharge')} +
    +

    + Teamer vor Ort +

    + + + +

    {teamerInCharge.name}

    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Teaser/OneColumn.html b/packages/ep_theme/Resources/Private/Partials/Teaser/OneColumn.html new file mode 100644 index 0000000..d2e520b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Teaser/OneColumn.html @@ -0,0 +1,34 @@ + + + +
    +
    + + {ep:icon(icon: 'flag-{context.country.code -> f:format.case(mode: \'lower\')}', class: 'w-8 h-6 absolute top-0 left-0')} + + +
    +
    + + {data.header -> f:format.nl2br()}{f:if(condition: data.subheader, then: '
    {data.subheader}')} + +
    +
    + {data.bodytext -> f:format.html()} +
    +
    + {data.button -> v:or(alternative: 'Details')} +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Teaser/TwoColumn.html b/packages/ep_theme/Resources/Private/Partials/Teaser/TwoColumn.html new file mode 100644 index 0000000..0bf16d1 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Teaser/TwoColumn.html @@ -0,0 +1,27 @@ + + + +
    +
    + +
    + +
    + {data.button -> v:or(alternative: 'Details')} +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/TeaserImage.html b/packages/ep_theme/Resources/Private/Partials/TeaserImage.html new file mode 100644 index 0000000..ed21a4d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/TeaserImage.html @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/TeaserWideImage.html b/packages/ep_theme/Resources/Private/Partials/TeaserWideImage.html new file mode 100644 index 0000000..9054c5f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/TeaserWideImage.html @@ -0,0 +1,27 @@ + + + + + + + + {image.alternative} + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Textmedia/Default.html b/packages/ep_theme/Resources/Private/Partials/Textmedia/Default.html new file mode 100644 index 0000000..24d7f7c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Textmedia/Default.html @@ -0,0 +1,18 @@ + + + + + {data.bodytext -> f:format.html()} + + + {files.0.originalResource.alternative} + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Textmedia/LinkPanel.html b/packages/ep_theme/Resources/Private/Partials/Textmedia/LinkPanel.html new file mode 100644 index 0000000..f19e61b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Textmedia/LinkPanel.html @@ -0,0 +1,74 @@ + + + +
    +
    + +
    + + +
    + {images.0.title -> f:format.raw()} +
    +
    +
    + +
    +
    + +
    + + +
    + {images.1.title -> f:format.raw()} +
    +
    +
    + +
    +
    + +
    + + +
    + {images.4.title -> f:format.raw()} +
    +
    +
    + +
    +
    + +
    + + +
    + {images.2.title -> f:format.raw()} +
    +
    +
    + +
    +
    + +
    + + +
    + {images.3.title -> f:format.raw()} +
    +
    +
    + +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Textmedia/Partnerlogos.html b/packages/ep_theme/Resources/Private/Partials/Textmedia/Partnerlogos.html new file mode 100644 index 0000000..55a62ee --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Textmedia/Partnerlogos.html @@ -0,0 +1,37 @@ + + + +
    + +
    + + + +
    + +
    + + + +
    +
    +
    +
    + + +
    + + + +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Topnav.html b/packages/ep_theme/Resources/Private/Partials/Topnav.html new file mode 100644 index 0000000..f28f42d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Topnav.html @@ -0,0 +1,50 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Partials/Travelinfo.html b/packages/ep_theme/Resources/Private/Partials/Travelinfo.html new file mode 100644 index 0000000..39924d3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Travelinfo.html @@ -0,0 +1,532 @@ + + + +
    +
    + +
    +
    +
    + Reiseinformation +
    + +
    +
    +
    +
    +

    + {travelInfo.subline} + {travelInfo.headline} +

    + {travelInfo.introText -> f:format.html()} + +

    + {travelData.label} {travelData.dateFrom -> f:format.date(format: 'd.m.Y')} - {travelData.dateTo -> f:format.date(format: 'd.m.Y')} +

    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + Anreise +
    +
    +
    + + + +

    + Adresse +

    +

    + {travelData.hotel.name} +
    + {travelData.hotel.street} +
    + {travelData.hotel.country} {travelData.hotel.city} +

    +
    +
    + + + + + + + +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + + + + +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    + Eigenanreise +

    + {travelInfo.selfArrangedText -> f:format.html()} +
    +
    +
    +
    +
    +

    + Sei vorbereitet! + Organisatorisches +

    +
    +
    + + + +
    +
    +
    + +
    +
    +
    + +
    +
    + Weitere Infos +
    +
    +
    + {travelInfo.additionalInfo -> f:format.html()} +
    +
    +
    + +
    +
    +
    + Wichtige Telefon­nummern +
    +
    +
    + +

    + Busbegleitung / Reiseleitung +
    + {travelData.guide.name} {travelData.guide.phone} +

    +
    + {travelInfo.importantPhoneNumbers -> f:format.html()} +
    +
    +
    + +
    +
    +
    + Wichtige Links vor Ort +
    +
    +
    + {travelInfo.importantLinks -> f:format.html()} +
    +
    +
    +
    +
    +
    + {travelInfo.footerText -> f:format.html()} + +
    +
    + +
    +
    + + +
    +
    + + + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +

    + {teaser.title} +

    + + + + +
      + +
    • + {item.label} +
    • +
      +
    +
    +
    +
    + + + +

    + Folgende Kurse finden statt +

    +
      + +
    • + {course.label} +
    • +
      +
    +
    + +

    + Bei Eurer Reise finden keine Kurse statt. +

    +
    +
    +
    +
    + {teaser.bodytext -> f:format.html()} +
    +
    + +
    +
    +
    +
    +
    + + + +
    + +
    +
    + {item.title -> f:format.raw()} +
    +
    +
    + + +

    + Bus-Zustiege +

    +

    + Bitte findet euch mindestens 20 Minuten vor Abfahrt an der gebuchten Haltestelle ein. +

    + {travelInfo.busInfoText -> f:format.html()} + + + + + + + + + + + +
    + Zustieg + + Datum/Uhrzeit +
    + {pickup.city}
    {pickup.location} +
    + {pickup.datetime -> f:format.date(format: 'd.m.Y')}
    {pickup.datetime -> f:format.date(format: 'H:i')} Uhr +
    + +

    + Die Rückreise mit dem Bus startet {departureDayTime}. +

    +
    + {travelInfo.departureText -> f:format.html()} +
    + + +

    + Bus-Zustiege +

    +

    + Bitte findet euch mindestens 20 Minuten vor Abfahrt an der gebuchten Haltestelle ein. +

    + {travelInfo.busInfoText -> f:format.html()} + + + + + + + + + + + +
    + Zustieg + + Datum/Uhrzeit +
    + {pickup.city}
    {pickup.street} +
    + {pickup.time -> f:format.date(format: 'd.m.Y')}
    {pickup.time -> f:format.date(format: 'H:i')} Uhr +
    + +

    + Die Rückreise mit dem Bus startet {departureDayTime}. +

    +
    + {travelInfo.departureText -> f:format.html()} +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceCalendar.html b/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceCalendar.html new file mode 100644 index 0000000..a2b1062 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceCalendar.html @@ -0,0 +1,76 @@ + + + + +
    + Der Kalender konnte aktuell nicht geladen werden. +
    + +
    + +
    + + +
    +
    + +
    +
    + {month.date} +
    + + + + + + + + + + + + + + + + + + + + + +
    MoDiMiDoFrSaSo
    + + + + + + + +
     
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceConfig.html b/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceConfig.html new file mode 100644 index 0000000..5bd064e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceConfig.html @@ -0,0 +1 @@ +{priceConfig -> f:format.json() ->f:format.raw()} diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceTable.html b/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceTable.html new file mode 100644 index 0000000..179997d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceTable.html @@ -0,0 +1,79 @@ + + + +
    +

    + Preise {year} +

    + + + + + + + + + + + + + + + + + + + + + + + +
    + von + + bis + + Saison + + Pax bis + + Mindestmiete pro Nacht + + weitere Personen pro Nacht +
    + {row.dateFrom -> f:format.date(format: 'd.m.Y')} + + {row.dateTo -> f:format.date(format: 'd.m.Y')} + + + VNS + NS + HS + {row.season} + + + {row.includedPax} + + + + + {row.defaultPricePerNight -> f:format.currency(currencySign: row.currency)} + + {row.pricePerNight -> f:format.currency(currencySign: row.currency)} + + + {row.pricePerNight -> f:format.currency(currencySign: row.currency)} + + + + {row.priceAdditionalPerson -> f:format.currency(currencySign: row.currency)} +
    +
    + VNS = Vorteils-Nebensaison, NS = Nebensaison, HS = Hauptsaison +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/Range.html b/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/Range.html new file mode 100644 index 0000000..f91c095 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxCalendar/Range.html @@ -0,0 +1,57 @@ + + + + + +
    + +
    + Der Kalender konnte aktuell nicht geladen werden. +
    +
    + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + +
    + {weekday} +
    + + + + + +   + + +
    +
    + {month.date} +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxFilterPanel/Index.html b/packages/ep_theme/Resources/Private/Templates/AjaxFilterPanel/Index.html new file mode 100644 index 0000000..9aad6b6 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxFilterPanel/Index.html @@ -0,0 +1,32 @@ + + + + + + + + + + + +
    + +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxSearch/Searchresult.html b/packages/ep_theme/Resources/Private/Templates/AjaxSearch/Searchresult.html new file mode 100644 index 0000000..6c17236 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxSearch/Searchresult.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxTable/Datestable.html b/packages/ep_theme/Resources/Private/Templates/AjaxTable/Datestable.html new file mode 100644 index 0000000..747389a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxTable/Datestable.html @@ -0,0 +1,335 @@ + + + + + + + {f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')} + +
    + + + + +
    + {nonBookableInfo.text -> f:format.html()} +
    +
    +
    +
    + +
    + Buchungsoptionen {hotel.name} +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Termin + + Nächte + + inklusive + + ab +
    + + + + + + + {row.dateStart} - {row.dateEnd} + + + + + + {row.nights} + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + + + {row.pseudoPrice -> ep:price()} € + + + + {row.minPrice -> ep:price()} + + + + + + + + + + + Reisen-Alert + + + + +
    + +
      + +
    • + +
    • +
      + +
    • + +
    • +
      + +
    • + +
    • +
      + +
    • + + + + + + {altLabel} + + +
    • +
      +
    + + + + + +
    +
    + + +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxTable/DaytripRooms.html b/packages/ep_theme/Resources/Private/Templates/AjaxTable/DaytripRooms.html new file mode 100644 index 0000000..f5d03e4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxTable/DaytripRooms.html @@ -0,0 +1,126 @@ + + + + + + + +

    + Bitte mindestens 2 Nächte auswählen. +

    +
    + + + +
    +
    > + + + + + + + + + + + + + + + + + + + + + +
    + Zimmerart + + Preis + + inkl. + +
    + {row.label} + + ab {row.priceForSelection} € + + + + + + + + + + + + Buchen + +
    + + + +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxTable/EventPricetable.html b/packages/ep_theme/Resources/Private/Templates/AjaxTable/EventPricetable.html new file mode 100644 index 0000000..e4c112c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxTable/EventPricetable.html @@ -0,0 +1,87 @@ + + + + + + + + + + +
    + {nonBookableInfo.text -> f:format.html()} +
    +
    +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + +
    + {category} + + {type} +
    + + + + {row.hotelShortName} + + + + + {row.hotelTitle} + + + {row.hotelName} + + + + + + + + + + {row.rooms.{type}.price -> ep:price()} + {f:if(condition: '{row.rooms.{type}.undersubscription}', then: '*')} + + + + + + {row.rooms.{type}.price -> ep:price()} + + + + +
    +

    * Preis kommt durch eine Unterbelegung zustande.

    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxTable/Pricetable.html b/packages/ep_theme/Resources/Private/Templates/AjaxTable/Pricetable.html new file mode 100644 index 0000000..8061e64 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxTable/Pricetable.html @@ -0,0 +1,325 @@ + + + + + +
    + Buchungsoptionen {hotel.name} +
    + +
    + + + + +
    + {nonBookableInfo.text -> f:format.html()} +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + Zimmertyp {dateRange} + + + inkl. + + + + optional + + Preis/Buchen +
    + +
      + +
    • + +
    • +
      + +
    • + +
    • +
      + + +
    • + +
    • +
      +
      +
    +
    + Inklusivleistungen +
    +
    +
      + +
    • + + + + {service} +
    • +
      +
    + +
    +
    + + + + +
    + ab {product.minPrice} € +
    +
    +
    +
    +
    + +
    + + +
    + + + + + + + + {row.roomName} + + + + {row.roomName} + + + ausgebucht + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + + + + + + + + + + + + + {row.minPrice -> ep:price()} + + + + + + {row.minPrice -> ep:price()} € + + + + + + + Reisen-Alert + + + + + +
    + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxTable/PricetableHtml.html b/packages/ep_theme/Resources/Private/Templates/AjaxTable/PricetableHtml.html new file mode 100644 index 0000000..1e40c9d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxTable/PricetableHtml.html @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxTeaserPopup/Concept.html b/packages/ep_theme/Resources/Private/Templates/AjaxTeaserPopup/Concept.html new file mode 100644 index 0000000..fe92df4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxTeaserPopup/Concept.html @@ -0,0 +1,43 @@ + + + + + +
    +
    +
    +
    + + {concept.popupTitle} + + +
    +
    +
    + +
    +

    + {concept.name} +

    + {concept.popupText -> f:format.html()} + + {concept.popupButtonLabel} + +
    +
    +
    +
    +
    + +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/AjaxWatchlist/List.html b/packages/ep_theme/Resources/Private/Templates/AjaxWatchlist/List.html new file mode 100644 index 0000000..459287a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/AjaxWatchlist/List.html @@ -0,0 +1,44 @@ + + + + +
    + Deine Merkliste ist aktuell leer... +
    +
    + + +
    +

    + {section.concept.name} +

    +
    + + + +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Backend/ListPages.html b/packages/ep_theme/Resources/Private/Templates/Backend/ListPages.html new file mode 100644 index 0000000..30936a0 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Backend/ListPages.html @@ -0,0 +1,54 @@ + + + + + + +

    SEO Übersicht

    + + + + + + + + + + + + + + + + + + + + +
    SeiteSEO TitleMeta Description
    + + {page.title} + + + + + + {page.seoTitle} + + {page.description} + +
    + + + +
    +
    +
    + +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/City/Detail.html b/packages/ep_theme/Resources/Private/Templates/City/Detail.html new file mode 100644 index 0000000..5456d92 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/City/Detail.html @@ -0,0 +1,31 @@ + + + + + + + +
    + +
    +
    + +
    +
    + + + + + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/City/Teasergroup.html b/packages/ep_theme/Resources/Private/Templates/City/Teasergroup.html new file mode 100644 index 0000000..4b75bdb --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/City/Teasergroup.html @@ -0,0 +1,46 @@ + + + + + + + +
    + +
    + + +
    +
    + + + +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Concept/Detail.html b/packages/ep_theme/Resources/Private/Templates/Concept/Detail.html new file mode 100644 index 0000000..5545164 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Concept/Detail.html @@ -0,0 +1,123 @@ + + + + + + + + + + + +
    + +
    +
    +
    + {concept.description -> f:format.html()} +
    + + + +
    +
    + + +
    +
    + + + +
    + + +
    + + Alle anzeigen + +
    +
    +
    +
    +
    + + + + + +
    + +
    + {concept.descriptionExtended -> f:format.html()} +
    +
    + + + + + +
    +
    +
      + + + +
    • + + {page.title} + +
    • +
      +
      +
      +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Concept/Teasergroup.html b/packages/ep_theme/Resources/Private/Templates/Concept/Teasergroup.html new file mode 100644 index 0000000..78e557b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Concept/Teasergroup.html @@ -0,0 +1,46 @@ + + + + + + + + +
    +
    + + +
    +
    + + + +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Container/Quarters.html b/packages/ep_theme/Resources/Private/Templates/Container/Quarters.html new file mode 100644 index 0000000..d1c347d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Container/Quarters.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + + +
    +
    + +
    +
    +
    +
    +
    +
    + + +
    + +
    + + +
    +
    + + {record.renderedContent -> f:format.raw()} + +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Container/Teasergroup.html b/packages/ep_theme/Resources/Private/Templates/Container/Teasergroup.html new file mode 100644 index 0000000..ab4148a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Container/Teasergroup.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + + +
    +
    + +
    +
    +
    +
    +
    +
    + + +
    + +
    + + +
    +
    + + {record.renderedContent -> f:format.raw()} + +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Country/Detail.html b/packages/ep_theme/Resources/Private/Templates/Country/Detail.html new file mode 100644 index 0000000..fddc6b4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Country/Detail.html @@ -0,0 +1,130 @@ + + + + + + + +
    + +
    +
    +
    + {country.description -> f:format.html()} +
    + +
    + + +
    + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    +
    + + +
    + + Alle anzeigen + +
    +
    +
    +
    +
    + +
    + {country.descriptionExtended -> f:format.html()} +
    +
    + + + +
    +
    + + + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Email/Autoreply.html b/packages/ep_theme/Resources/Private/Templates/Email/Autoreply.html new file mode 100644 index 0000000..0db083c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Email/Autoreply.html @@ -0,0 +1,15 @@ +
    + + + + +

    Sehr geehrte Damen und Herren,

    +

    auf Grund einer internen Schulung ist unser Büro bis Dienstag, 24.04.2018 nicht besetzt.

    +

    In dringenden Fällen schreiben Sie eine Mail an nils.klems@ep-reisen.de, die E-Mails werden nur in + unregelmäßigen Abständen gelesen.

    +

    Vielen Dank.

    +
    + +
    diff --git a/packages/ep_theme/Resources/Private/Templates/Email/ContactForm.html b/packages/ep_theme/Resources/Private/Templates/Email/ContactForm.html new file mode 100644 index 0000000..74c4cd0 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Email/ContactForm.html @@ -0,0 +1,65 @@ +
    + + + + +

    Anfrage E&P Reisen vom

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Seite{contactForm.pageUrl}
    Name{contactForm.name}
    Vorname{contactForm.firstName}
    Geschlecht{contactForm.gender}
    Firma/Verein{contactForm.company -> v:or(alternative: 'Keine Angabe')}
    E-Mail{contactForm.email}
    Telefon{contactForm.phone}
    Zeitraum{contactForm.period -> v:or(alternative: 'Keine Angabe')}
    Aufenthaltsdauer{contactForm.duration -> v:or(alternative: 'Keine Angabe')}
    Anzahl der Personen{contactForm.pax -> v:or(alternative: 'Keine Angabe')}
    Durchschnittsalter{contactForm.age -> v:or(alternative: 'Keine Angabe')}
    Kinder{contactForm.children -> v:or(alternative: 'Nein')}
    Anfrage/Wunsch{contactForm.inquiry -> v:or(alternative: 'Keine Angabe')}
    +
    + +
    diff --git a/packages/ep_theme/Resources/Private/Templates/Email/GroupsPrice.html b/packages/ep_theme/Resources/Private/Templates/Email/GroupsPrice.html new file mode 100644 index 0000000..54aa868 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Email/GroupsPrice.html @@ -0,0 +1,157 @@ +
    + + + + + + +

    Buchung Gruppenhaus vom

    +
    + +

    Anfrage Gruppenhaus vom

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Art + + {f:if(condition: '{mode} == "booking"', then: 'Buchung', else: 'Anfrage')} +
    Haus{hotel.name}
    Name{name}
    Name der Gruppe{group}
    Straße{street}
    Postleitzahl{postcode}
    Ort{city}
    E-Mail{email}
    Telefon{phone -> v:or(alternative: '-')}
    Zeitraum{dateFrom} - {dateTo}
    Nächte{nights}
    Anzahl der Personen{pax -> v:or(alternative: '-')}
    Anzahl Kinder 0-3 Jahre{children -> v:or(alternative: '-')}
    Anzahl Kinder 4-5 Jahre{minors -> v:or(alternative: '-')}
    Anzahl Kinder 6-{adolescentsAge} Jahre{adolescents -> v:or(alternative: '-')}
    Verpflegung + + + {board.title} + + + - + + +
    Optionale Zusatzleistungen + + +
      + +
    • {option}
    • +
      +
    +
    + + - + +
    +
    Kostenübersicht +
      +
    • + Grundpreis: {summary.paxBase} +
    • +
    • + Aufpreis Personen: {summary.paxAdditional -> v:or(alternative: '-')} +
    • +
    • + Verpflegung: {summary.board -> v:or(alternative: '-')} +
    • +
    • + Kleingruppen-Verpflegungszuschlag: {summary.undersubscription -> v:or(alternative: '-')} +
    • +
    • + Aufpreis Kurzzeit: {summary.shortTerm -> v:or(alternative: '-')} +
    • +
    • + Strom- und Abfallgebühren: {summary.runningCosts -> v:or(alternative: '-')} +
    • +
    • + Gesamtpreis: {summary.total} +
    • +
    +
    Bemerkungen/Wünsche + + + {remarks -> f:format.nl2br()} + + + - + + +
    +
    + +
    diff --git a/packages/ep_theme/Resources/Private/Templates/Faq/Detail.html b/packages/ep_theme/Resources/Private/Templates/Faq/Detail.html new file mode 100644 index 0000000..a027350 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Faq/Detail.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Button.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Button.html new file mode 100644 index 0000000..70d2083 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Button.html @@ -0,0 +1,16 @@ + + + + + + + + {data.header} + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Calendar.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Calendar.html new file mode 100644 index 0000000..2bc5286 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Calendar.html @@ -0,0 +1,30 @@ + + + + + +
    +

    Belegungskalender {hotel.0.data.name}

    +
    + +
    +
    + + + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Contact.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Contact.html new file mode 100644 index 0000000..3c44074 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Contact.html @@ -0,0 +1,31 @@ + + + + + +
    +
    +
    + + + +
    +
    + {contact.0.data.name} +
    {contact.0.data.tasks -> f:format.nl2br()}
    +
    {contact.0.data.phone}
    +
    E-Mail
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/DealOfTheWeek.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/DealOfTheWeek.html new file mode 100644 index 0000000..58dd7b2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/DealOfTheWeek.html @@ -0,0 +1,47 @@ + + + + + +
    +
    + +
    +
    + + SchneEPchen + +
    +
    +
    +
    + Angebot +
    + + {data.header} + +
    + {data.bodytext -> f:format.nl2br()} +
    + + + Details + + +
    +
    +
    + +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Disrupter.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Disrupter.html new file mode 100644 index 0000000..5fe1e8b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Disrupter.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Disturber.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Disturber.html new file mode 100644 index 0000000..9b5c115 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Disturber.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/EventPricetable.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/EventPricetable.html new file mode 100644 index 0000000..b811601 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/EventPricetable.html @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/FaqOverview.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/FaqOverview.html new file mode 100644 index 0000000..85be72c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/FaqOverview.html @@ -0,0 +1,41 @@ + + + + + + +
    +
    +
    + + {ep:icon(icon: 'search', class: 'w-6 h-6 absolute top-2 right-2')} +
    + + + + + +
    + {ep:icon(icon: 'info-circle', class: 'w-4 h-4')} + + Weitere Reiseinfos findet ihr hier + +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Gmap.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Gmap.html new file mode 100644 index 0000000..a8df501 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Gmap.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Header.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Header.html new file mode 100644 index 0000000..f64bb96 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Header.html @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Html.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Html.html new file mode 100644 index 0000000..e86662f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Html.html @@ -0,0 +1,12 @@ + + + + + + {data.bodytext} + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Lineup.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Lineup.html new file mode 100644 index 0000000..7ec6f5f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Lineup.html @@ -0,0 +1,71 @@ + + + + + +
    + +
    +
    + +
    +
    +
    + + {item.data.name -> f:format.raw()} + +
    + {item.data.description -> f:format.html()} +
    + +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSection.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSection.html new file mode 100644 index 0000000..a032b66 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSection.html @@ -0,0 +1,34 @@ + + + + + + + + +
    + + + + + + + + + +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSectionPages.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSectionPages.html new file mode 100644 index 0000000..ca80706 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSectionPages.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSubpages.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSubpages.html new file mode 100644 index 0000000..eaa4848 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSubpages.html @@ -0,0 +1,20 @@ + + + + + + + +
    + + + {page.data.title} + + +
    +
    + +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/NewsletterForm.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/NewsletterForm.html new file mode 100644 index 0000000..91fec97 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/NewsletterForm.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Shortcut.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Shortcut.html new file mode 100644 index 0000000..684e102 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Shortcut.html @@ -0,0 +1,10 @@ + + + + + + {shortcuts} + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Teaser.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Teaser.html new file mode 100644 index 0000000..b46ccc4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Teaser.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserBookingLink.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserBookingLink.html new file mode 100644 index 0000000..2f27986 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserBookingLink.html @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserCity.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserCity.html new file mode 100644 index 0000000..f1caa5a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserCity.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserConcept.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserConcept.html new file mode 100644 index 0000000..bb8e336 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserConcept.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserCountry.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserCountry.html new file mode 100644 index 0000000..aebcaea --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserCountry.html @@ -0,0 +1,37 @@ + + + + + +
    +
    + + + + + + +
    +
    + + {country.name} + + +
    + {country.teaser -> f:format.crop(maxCharacters: settings.teaserTextMaxChars) -> f:format.html()} +
    +
    + {data.button -> v:or(alternative: 'Details')} +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserHotel.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserHotel.html new file mode 100644 index 0000000..43470ac --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserHotel.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserProduct.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserProduct.html new file mode 100644 index 0000000..f5e435a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserProduct.html @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserRegion.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserRegion.html new file mode 100644 index 0000000..99a2846 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserRegion.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Text.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Text.html new file mode 100644 index 0000000..97abd62 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Text.html @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Textmedia.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Textmedia.html new file mode 100644 index 0000000..fd8f294 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Textmedia.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Textpic.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Textpic.html new file mode 100644 index 0000000..62c4211 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Textpic.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/YoutubeVideo.html b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/YoutubeVideo.html new file mode 100644 index 0000000..816f874 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/FluidStyledContent/YoutubeVideo.html @@ -0,0 +1,25 @@ + + + + + +
    + +
    +
    + Bitte Marketing-Cookies akzeptieren, um dieses Video anzusehen. +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Form/Form.html b/packages/ep_theme/Resources/Private/Templates/Form/Form.html new file mode 100644 index 0000000..ef9d15c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Form/Form.html @@ -0,0 +1,22 @@ + + + + +
    + +
    +
    +
    + diff --git a/packages/ep_theme/Resources/Private/Templates/Form/Render.html b/packages/ep_theme/Resources/Private/Templates/Form/Render.html new file mode 100644 index 0000000..cf636c9 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Form/Render.html @@ -0,0 +1,10 @@ + + + + +
    + +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Gridelements/HalfAndTwoQuarters.html b/packages/ep_theme/Resources/Private/Templates/Gridelements/HalfAndTwoQuarters.html new file mode 100644 index 0000000..d7a52a9 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Gridelements/HalfAndTwoQuarters.html @@ -0,0 +1,30 @@ + + + + + + + +
    +
    + {data.tx_gridelements_view_column_51 -> f:format.raw()} +
    +
    +
    +
    + {data.tx_gridelements_view_column_52 -> f:format.raw()} +
    +
    + {data.tx_gridelements_view_column_53 -> f:format.raw()} +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Gridelements/Halves.html b/packages/ep_theme/Resources/Private/Templates/Gridelements/Halves.html new file mode 100644 index 0000000..43a4f10 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Gridelements/Halves.html @@ -0,0 +1,27 @@ + + + + + + + +
    + +
    + {data.tx_gridelements_view_column_21 -> f:format.raw()} +
    +
    + +
    + {data.tx_gridelements_view_column_22 -> f:format.raw()} +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Gridelements/Quarters.html b/packages/ep_theme/Resources/Private/Templates/Gridelements/Quarters.html new file mode 100644 index 0000000..4ba7caf --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Gridelements/Quarters.html @@ -0,0 +1,21 @@ + + + + + + + +
    + {data.tx_gridelements_view_column_41 -> f:format.raw()} + {data.tx_gridelements_view_column_42 -> f:format.raw()} + {data.tx_gridelements_view_column_43 -> f:format.raw()} + {data.tx_gridelements_view_column_44 -> f:format.raw()} +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Gridelements/Thirds.html b/packages/ep_theme/Resources/Private/Templates/Gridelements/Thirds.html new file mode 100644 index 0000000..cbb4ec1 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Gridelements/Thirds.html @@ -0,0 +1,20 @@ + + + + + + + +
    + {data.tx_gridelements_view_column_31 -> f:format.raw()} + {data.tx_gridelements_view_column_32 -> f:format.raw()} + {data.tx_gridelements_view_column_33 -> f:format.raw()} +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Gridelements/TwoQuartersAndHalf.html b/packages/ep_theme/Resources/Private/Templates/Gridelements/TwoQuartersAndHalf.html new file mode 100644 index 0000000..3d349ce --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Gridelements/TwoQuartersAndHalf.html @@ -0,0 +1,30 @@ + + + + + + + +
    +
    +
    +
    + {data.tx_gridelements_view_column_52 -> f:format.raw()} +
    +
    + {data.tx_gridelements_view_column_53 -> f:format.raw()} +
    +
    +
    +
    + {data.tx_gridelements_view_column_51 -> f:format.raw()} +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/GroupsPrice/Index.html b/packages/ep_theme/Resources/Private/Templates/GroupsPrice/Index.html new file mode 100644 index 0000000..6dc6ec4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/GroupsPrice/Index.html @@ -0,0 +1,37 @@ + + +
    +

    + {hotel.name} + Preisberechnung & direkt buchen +

    + + + + + +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Hotel/DateIndependentTeasergroup.html b/packages/ep_theme/Resources/Private/Templates/Hotel/DateIndependentTeasergroup.html new file mode 100644 index 0000000..ce12341 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Hotel/DateIndependentTeasergroup.html @@ -0,0 +1,27 @@ + + + + + +
    + +
    +
    +
      + +
    • + +
    • +
      +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Hotel/Detail.html b/packages/ep_theme/Resources/Private/Templates/Hotel/Detail.html new file mode 100644 index 0000000..41a4977 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Hotel/Detail.html @@ -0,0 +1,37 @@ + + + + + + + +
    + +
    +
    + +
    +
    + + + + + +
    +

    Lage

    +
    + +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Hotel/DetailEvent.html b/packages/ep_theme/Resources/Private/Templates/Hotel/DetailEvent.html new file mode 100644 index 0000000..1a0b51c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Hotel/DetailEvent.html @@ -0,0 +1,39 @@ + + + + + + +
    +
    + +
    +
    + {hotel.description -> f:format.html()} +
    +
    +
    + +

    Buchungsoptionen

    +
    + +
    +
    +

    Ausstattung

    + {hotel.features -> f:format.html()} +

    Zimmerarten / Apartments

    + {hotel.roomTypes -> f:format.html()} + +

    Zusatzinformationen

    + {hotel.additionalInformation -> f:format.html()} +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Hotel/Teasergroup.html b/packages/ep_theme/Resources/Private/Templates/Hotel/Teasergroup.html new file mode 100644 index 0000000..86090cb --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Hotel/Teasergroup.html @@ -0,0 +1,54 @@ + + + + + + + + +
    +
    + + +
    +
    + + + + + + +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Map/General.html b/packages/ep_theme/Resources/Private/Templates/Map/General.html new file mode 100644 index 0000000..f829c83 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Map/General.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/News/News/Detail.html b/packages/ep_theme/Resources/Private/Templates/News/News/Detail.html new file mode 100644 index 0000000..a600b03 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/News/News/Detail.html @@ -0,0 +1,91 @@ + + + + + + + + + + + + + {newsItem.alternativeTitle} + + + + + {newsItem.title} + + + + + +
    +

    + {newsItem.title} +

    +

    + {newsItem.author} am + + in + + + {tag.title} + + {f:if(condition: iteration.isLast, else: ', ')} + + +

    + +

    + {newsItem.teaser} +

    +
    +
    + + + {newsItem.bodytext} + +
    + {newsItem.contentElementIdList} +
    +
    +
    +
    +
    + +

    + Dateien +

    + +
    + +

    + Ähnliche Beiträge +

    + + + +
    + + + + + +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/News/News/List.html b/packages/ep_theme/Resources/Private/Templates/News/News/List.html new file mode 100644 index 0000000..d7246b4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/News/News/List.html @@ -0,0 +1,51 @@ + + + + + + + + + + +

    + Blog: {tags.0.title} +

    +
    + +

    + Blog +

    +
    +
    +
    + + + + + + + + + + + + + + +
    + +
    + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/News/Tag/List.html b/packages/ep_theme/Resources/Private/Templates/News/Tag/List.html new file mode 100644 index 0000000..5db40bb --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/News/Tag/List.html @@ -0,0 +1,24 @@ + + + + + + +

    Themen

    +
      + +
    • + + + + + {tag.title} + +
    • +
      +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/News/ViewHelpers/Widget/Paginate/Index.html b/packages/ep_theme/Resources/Private/Templates/News/ViewHelpers/Widget/Paginate/Index.html new file mode 100644 index 0000000..34a0f6f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/News/ViewHelpers/Widget/Paginate/Index.html @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Newsletter/Index.html b/packages/ep_theme/Resources/Private/Templates/Newsletter/Index.html new file mode 100644 index 0000000..043bc54 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Newsletter/Index.html @@ -0,0 +1,46 @@ + + + + + +
    + + +
    Du hast Deine E-Mail-Adresse erfolgreich abgemeldet und erhältst in + Zukunft von uns keinen Newsletter mehr.
    +
    + +
    Parameterfehler - Deine E-Mail-Adresse konnte leider nicht + abgemeldet werden.
    Sollte dieser Fehler weiterhin auftreten, setze Dich bitte telefonisch mit + uns in Verbindung.
    +
    + +
    Vielen Dank - Du hast Deine E-Mail-Adresse erfolgreich für + unseren Newsletter registriert.
    +
    + +
    Parameterfehler - Deine E-Mail-Adresse konnte leider nicht + korrekt registriert werden.
    Sollte dieser Fehler weiterhin auftreten, setze Dich bitte + telefonisch mit uns in Verbindung.
    +
    + +
    Vielen Dank für Dein Interesse - Du erhältst in Kürze + eine Mail mit einem Bestätigungslink an Deine E-Mail-Adresse geschickt.
    +
    + +
    Deine E-Mail-Adresse ist bereits für unseren Newsletter + vorgemerkt - es fehlt nur noch Deine Bestätigung. Solltest Du noch keine Mail erhalten haben, prüfe + bitte auch die Spam-Einstellungen Deines Posteingangs.
    +
    + +
    Diese E-Mail-Adresse ist bereits für unseren Newsletter registriert.
    +
    + +
    Ein Problem ist aufgetreten. Bitte versuche es erneut.
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Newsletter/SubscriptionForm.html b/packages/ep_theme/Resources/Private/Templates/Newsletter/SubscriptionForm.html new file mode 100644 index 0000000..c66150f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Newsletter/SubscriptionForm.html @@ -0,0 +1,104 @@ + + + + + + +
    + +
    +
    + + + +
    + + + + + + +
    + +
    +
    +
    + + + + + +

    + News & Angebote per Mail +

    +
    + + + + + +

    + Benachrichtige mich bei Verfügbarkeit +

    +
    +
    +
    +
    + +
    + + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Newsletter/SubscriptionFormSubmit.html b/packages/ep_theme/Resources/Private/Templates/Newsletter/SubscriptionFormSubmit.html new file mode 100644 index 0000000..3e55f69 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Newsletter/SubscriptionFormSubmit.html @@ -0,0 +1,23 @@ + + + + + +
    +
    +

    + Vielen Dank für dein Interesse +

    +

    + Deine Anmeldung muss noch bestätigt werden. Bitte nutze den Link aus der Bestätigungs-E-Mail, die in + Kürze bei dir eintreffen sollte. +

    +

    + Sieh am besten auch im Spam-Ordner nach. +

    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/Detail.html b/packages/ep_theme/Resources/Private/Templates/Page/Detail.html new file mode 100644 index 0000000..d018f9f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/Detail.html @@ -0,0 +1,15 @@ + + + + + + + + + + |'}" /> + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/Frontpage.html b/packages/ep_theme/Resources/Private/Templates/Page/Frontpage.html new file mode 100644 index 0000000..4bf1a73 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/Frontpage.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/FrontpageEvent.html b/packages/ep_theme/Resources/Private/Templates/Page/FrontpageEvent.html new file mode 100644 index 0000000..2a9a0b3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/FrontpageEvent.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/Popup.html b/packages/ep_theme/Resources/Private/Templates/Page/Popup.html new file mode 100644 index 0000000..9a5aa8e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/Popup.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/Search.html b/packages/ep_theme/Resources/Private/Templates/Page/Search.html new file mode 100644 index 0000000..1001ea8 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/Search.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/Subpage.html b/packages/ep_theme/Resources/Private/Templates/Page/Subpage.html new file mode 100644 index 0000000..5954877 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/Subpage.html @@ -0,0 +1,15 @@ + + + + + + + + + + |'}" /> + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarLeft.html b/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarLeft.html new file mode 100644 index 0000000..f045f3f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarLeft.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + |'}" /> + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarLeftEvent.html b/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarLeftEvent.html new file mode 100644 index 0000000..cfdc0be --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarLeftEvent.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + |'}" /> + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarRight.html b/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarRight.html new file mode 100644 index 0000000..95ce454 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarRight.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + |'}" /> + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarRightEvent.html b/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarRightEvent.html new file mode 100644 index 0000000..c5b6ab8 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarRightEvent.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + |'}" /> + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Page/Travelinfo.html b/packages/ep_theme/Resources/Private/Templates/Page/Travelinfo.html new file mode 100644 index 0000000..a74b139 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Page/Travelinfo.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Password/Index.html b/packages/ep_theme/Resources/Private/Templates/Password/Index.html new file mode 100644 index 0000000..6d6fd87 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Password/Index.html @@ -0,0 +1,15 @@ + + + + + +
    +
    + +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Disrupter.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Disrupter.html new file mode 100644 index 0000000..e97e2f9 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Disrupter.html @@ -0,0 +1,11 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + Störer +

    + + {content.headline} + +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Disturber.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Disturber.html new file mode 100644 index 0000000..960ae76 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Disturber.html @@ -0,0 +1,24 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + Störer +

    + + + + groß + > + + Event klein + + + Event groß + + + klein + + + +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Gmap.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Gmap.html new file mode 100644 index 0000000..dcb9ca0 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Gmap.html @@ -0,0 +1,11 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + OSM Karte +

    + + {content.name} ({content.latitude}, {content.longitude}) + +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Lineup.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Lineup.html new file mode 100644 index 0000000..d91851c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Lineup.html @@ -0,0 +1,15 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + Lineup +

    +
      + +
    • + {row.name} +
    • +
      +
    +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserBookingLink.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserBookingLink.html new file mode 100644 index 0000000..e12ce44 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserBookingLink.html @@ -0,0 +1,23 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + Teaser Buchungslink +

    +

    + + + + + + + + +

    + + {product.name} + +
    + {product.teaser -> f:format.html()} +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserCity.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserCity.html new file mode 100644 index 0000000..b9a1206 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserCity.html @@ -0,0 +1,23 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + Teaser Ort +

    +

    + + + + + + + + +

    + + {city.name} + +
    + {city.teaser -> f:format.html()} +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserConcept.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserConcept.html new file mode 100644 index 0000000..cb8d844 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserConcept.html @@ -0,0 +1,23 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + Teaser Konzept +

    +

    + + + + + + + + +

    + + {concept.name} + +
    + {concept.teaser -> f:format.html()} +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserCountry.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserCountry.html new file mode 100644 index 0000000..126aa0e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserCountry.html @@ -0,0 +1,23 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + Teaser Land +

    +

    + + + + + + + + +

    + + {country.name} + +
    + {country.teaser -> f:format.html()} +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserHotel.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserHotel.html new file mode 100644 index 0000000..731bdff --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserHotel.html @@ -0,0 +1,23 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + Teaser Hotel +

    +

    + + + + + + + + +

    + + {hotel.name} + +
    + {hotel.teaser -> f:format.html()} +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserProduct.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserProduct.html new file mode 100644 index 0000000..40feb7c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserProduct.html @@ -0,0 +1,23 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + Teaser Produkt +

    +

    + + + + + + + + +

    + + {product.name} + +
    + {product.teaser -> f:format.html()} +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserRegion.html b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserRegion.html new file mode 100644 index 0000000..8ad9aa6 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserRegion.html @@ -0,0 +1,23 @@ +{namespace be=TYPO3\CMS\Backend\ViewHelpers} +
    +
    +

    + Teaser Gebiet +

    +

    + + + + + + + + +

    + + {region.name} + +
    + {region.teaser -> f:format.html()} +
    +
    diff --git a/packages/ep_theme/Resources/Private/Templates/Pricetable/Event.html b/packages/ep_theme/Resources/Private/Templates/Pricetable/Event.html new file mode 100644 index 0000000..bcd6f42 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Pricetable/Event.html @@ -0,0 +1,15 @@ + + + + + +
    + +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Product/Detail.html b/packages/ep_theme/Resources/Private/Templates/Product/Detail.html new file mode 100644 index 0000000..2e1e818 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Product/Detail.html @@ -0,0 +1,372 @@ + + + + + +
    + + +
    + + data-controller="product-details" + data-action="tabs:selected->product-details#switchView" + data-product-details-datestable-uri-value="{ep:uri.ajax(action: 'datestable', controller: 'AjaxTable', arguments: '{product: product, hotel: hotel, bpnBookingUrlTemplateCode: settings.bpnBookingUrlTemplateCode}', format: 'html', pageUid: settings.defaultAjaxUid)}" + data-product-details-disable-tables-value="{f:if(condition: hotel.groupsPriceConfigs, then: 'true', else: 'false')}" + + + > + +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    + + +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    + + +
    + {nonBookableInfo.text -> f:format.html()} +
    +
    + + + + + + + + + + + + + + + +
    +

    + Gruppenrabatt bis zu 100€ möglich +

    +

    + Für Gruppen bis 15 Personen könnt ihr hier direkt buchen. + Für Gruppen ab 16 Personen erstellen wir euch ein konkretes Angebot zu günstigen + Gruppenkonditionen zu dieser Reise. Anfrage via Tel. 0221 - 272 276 18 oder mit einer + Kurzanfrage! +

    +
    +
    +
    + + + + + + + + + + + + +
    + + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    + 360°-Rundgang +

    + + + + + + +
    + öffnen +
    +
    +
    +
    +
    +
    + + +
    + +
    +
    + Bitte Marketing-Cookies akzeptieren, um dieses Video anzusehen. +
    +
    +
    +
    +
    +

    + Lage +

    + +
    + + + + +
    + + + +
    +
    + + + + +
    + + Zum Anfrageformular + +
    +
    + + + + + +
    + + +
    +
    + Buchungsoptionen {product.name} +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    + Bitte einen Zeitraum auswählen. +
    +
    +
    +
    +
    + + +
    + + + + +
    +
    + + + +
    +

    + Galerie +

    +
    + + + + + +
    +
    +
    +
    + + + + + + + +
    + +
    +
    + + +
    + + Preistabelle wird geladen … +
    +
    + diff --git a/packages/ep_theme/Resources/Private/Templates/Product/HotelList.html b/packages/ep_theme/Resources/Private/Templates/Product/HotelList.html new file mode 100644 index 0000000..aa85f86 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Product/HotelList.html @@ -0,0 +1,77 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + {product.teaserLong -> f:format.html()} +
    + + +
    + Wähle deine Unterkunft +
    +
    + + + +
    +
    + +
    + + + +
    + Die von dir gewählte Reise ist aktuell nicht buchbar, aber wir helfen dir gerne dabei, + eine andere Reise zu finden. Ruf uns an unter {settings.phoneNumber} + oder schreibe an . +
    +
    +
    +
    +
    +
    + + + + + + + + + + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Product/Pricetable.html b/packages/ep_theme/Resources/Private/Templates/Product/Pricetable.html new file mode 100644 index 0000000..21a47dc --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Product/Pricetable.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Product/Teasergroup.html b/packages/ep_theme/Resources/Private/Templates/Product/Teasergroup.html new file mode 100644 index 0000000..0205370 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Product/Teasergroup.html @@ -0,0 +1,118 @@ + + + + + +
    +
    + + + + + +
    + + + +
    +
    + +
    + + +
    +
    + + + +
    +
    +
    + + + +
    + + Alle anzeigen + +
    +
    +
    +
    +
    + + +
    + {nonBookableInfo.text -> f:format.html()} +
    +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Region/Detail.html b/packages/ep_theme/Resources/Private/Templates/Region/Detail.html new file mode 100644 index 0000000..ad9d8f5 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Region/Detail.html @@ -0,0 +1,34 @@ + + + + + + + +
    + +
    +
    + +
    +
    + + + + + + + + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Region/Teasergroup.html b/packages/ep_theme/Resources/Private/Templates/Region/Teasergroup.html new file mode 100644 index 0000000..b1a26cc --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Region/Teasergroup.html @@ -0,0 +1,46 @@ + + + + + + + +
    + +
    + + +
    +
    + + + +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Reseller/Copypaste.html b/packages/ep_theme/Resources/Private/Templates/Reseller/Copypaste.html new file mode 100644 index 0000000..31eb070 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Reseller/Copypaste.html @@ -0,0 +1,345 @@ + + + + + +
    +

    Datenexport {productData.name}

    +
    + + + + + + + + + + + + + + + + + + + +
    + zurück +
    +
    + + +
    +
    +

    + +

    +
    + +
    +
    + + +
    +
    +

    + +

    +
    + +
    +
    + + +
    +
    +

    + +

    +
    + +
    +
    + + +
    +
    +

    + +

    +
    + +
    +
    + + +
    +
    +

    + +

    +
    + +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Reseller/List.html b/packages/ep_theme/Resources/Private/Templates/Reseller/List.html new file mode 100644 index 0000000..d8d72ef --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Reseller/List.html @@ -0,0 +1,33 @@ + + + + + +
    + +

    {reseller.name}

    +

    Gesamtexport XML

    + + Export XML + +

    Gesamtexport CSV (Facebook)

    + + Export CSV (Google) + +

    Gesamtexport CSV (Google)

    + + Export CSV (Facebook) + +

    Einzelexport für Copy&Paste

    +
      + +
    • {product.nameInternal}
    • +
      +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Search/Searchresult.html b/packages/ep_theme/Resources/Private/Templates/Search/Searchresult.html new file mode 100644 index 0000000..c5c760d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Search/Searchresult.html @@ -0,0 +1,29 @@ + + + + + +
    +
    +
    + +
    + +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Searchbar/Index.html b/packages/ep_theme/Resources/Private/Templates/Searchbar/Index.html new file mode 100644 index 0000000..de17575 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Searchbar/Index.html @@ -0,0 +1,279 @@ + + + + + +
    + data-searchbar-collapsed-value="true" + + data-action="datepicker:range-select->searchbar#setDateRange searchbar-toggle:toggle@window->searchbar#toggle"> + + + + + + + +
    + + data-controller="sticky" + data-sticky-offset-value="650" + data-sticky-padding-value="7.5rem" + data-sticky-disabled-value="false" + data-sticky-classes-value="sticky reduced" + + + data-searchbar-target="container"> +
    +
    +
    + +
    +
    + + + + + +
    +
    + +
    +
    + + + + + +
    +
    + +
    +
    + + + + + +
    +
    + +
    +
    + + + + + +
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Snowreport/List.html b/packages/ep_theme/Resources/Private/Templates/Snowreport/List.html new file mode 100644 index 0000000..1da3b32 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Snowreport/List.html @@ -0,0 +1,53 @@ + + + + + + +
    + +
    +
    + {ep:icon(icon: 'flag-{snowreport.countryCode -> f:format.case(mode: \'lower\')}', class: 'w-8 h-6')} + + {snowreport.regionName} + +
    +
    + + +
      +
    • + Berg/Tal: {snowreport.snow_level_mountain -> v:or(alternative: '-')}/{snowreport.snow_level_valley -> v:or(alternative: '-')} cm +
    • +
    • + Qualität: {snowreport.snow_quality} +
    • +
    • + Geöffnete Lifte: {snowreport.lifts_opened}/{snowreport.lifts_total}keine Angabe +
    • +
    • + Letzter Schneefall: {f:if(condition: snowreport.last_snowfall, then: '{snowreport.last_snowfall -> f:format.date(format: \'%d.%m.%Y\')}', else: 'keine Angabe')} +
    • +
    • + Letzte Akt.: {snowreport.last_update -> f:format.date(format: '%d.%m.%Y')} +
    • +
    +
    + + Saison vom {snowreport.seasonStart -> f:format.date(format: '%d.%m.%Y')} bis {snowreport.seasonEnd -> f:format.date(format: '%d.%m.%Y')} + +
    +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Team/List.html b/packages/ep_theme/Resources/Private/Templates/Team/List.html new file mode 100644 index 0000000..d3edd32 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Team/List.html @@ -0,0 +1,49 @@ + + + + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Team/Show.html b/packages/ep_theme/Resources/Private/Templates/Team/Show.html new file mode 100644 index 0000000..645862b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Team/Show.html @@ -0,0 +1,48 @@ + + + + + +
    +
    +
    + + + +
    +
    + +
    + {member.description -> f:format.html()} +
    + +
    + + + + + + {member.email} + + + + + + + + {member.phone} + + +
    +
    + + zurück + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelCode.html b/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelCode.html new file mode 100644 index 0000000..9015bcd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelCode.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelInfo.html b/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelInfo.html new file mode 100644 index 0000000..9015bcd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelInfo.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Watchlist/List.html b/packages/ep_theme/Resources/Private/Templates/Watchlist/List.html new file mode 100644 index 0000000..ccb69c5 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Watchlist/List.html @@ -0,0 +1,23 @@ + + + + + + +
    +
    + +
    +
    +
    + + diff --git a/packages/ep_theme/composer.json b/packages/ep_theme/composer.json new file mode 100644 index 0000000..cacc62f --- /dev/null +++ b/packages/ep_theme/composer.json @@ -0,0 +1,32 @@ +{ + "name": "ep/ep-theme", + "description": "Theme package for Ep Reisen TYPO3 website", + "license": "GPL-2.0-or-later", + "type": "typo3-cms-extension", + "authors": [ + { + "name": "Björn Fromme", + "email": "fromme@dreipunktnull.com" + } + ], + "require": { + "php": "^8.2", + "b13/container": "^4.1", + "ep/ep-products": "@dev", + "typo3/cms-core": "^13.4", + "typo3/cms-extbase": "^13.4", + "typo3/cms-fluid": "^13.4", + "typo3/cms-fluid-styled-content": "^13.4", + "typo3/cms-frontend": "^13.4" + }, + "autoload": { + "psr-4": { + "EP\\EpTheme\\": "Classes/" + } + }, + "extra": { + "typo3/cms": { + "extension-key": "ep_theme" + } + } +} diff --git a/packages/ep_theme/ext_emconf.php b/packages/ep_theme/ext_emconf.php new file mode 100644 index 0000000..0f46223 --- /dev/null +++ b/packages/ep_theme/ext_emconf.php @@ -0,0 +1,21 @@ + 'E&P Theme', + 'description' => 'Theme package for Ep Reisen TYPO3 website', + 'category' => 'plugins', + 'author' => 'Björn Fromme', + 'author_email' => 'fromme@dreipunktnull.com', + 'state' => 'beta', + 'internal' => '', + 'uploadfolder' => true, + 'createDirs' => '', + 'dividers2tabs' => true, + 'clearCacheOnLoad' => 0, + 'version' => '3.0.0', + 'constraints' => [ + 'depends' => [], + 'conflicts' => [], + 'suggests' => [] + ], +]; diff --git a/packages/ep_theme/ext_icon.gif b/packages/ep_theme/ext_icon.gif new file mode 100644 index 0000000..9fc0f1d Binary files /dev/null and b/packages/ep_theme/ext_icon.gif differ diff --git a/packages/ep_theme/ext_localconf.php b/packages/ep_theme/ext_localconf.php new file mode 100644 index 0000000..453750a --- /dev/null +++ b/packages/ep_theme/ext_localconf.php @@ -0,0 +1,63 @@ + [ + 'TYPO3\\CMS\\Core\\Log\\Writer\\FileWriter' => [ + 'logFile' => \TYPO3\CMS\Core\Core\Environment::getVarPath() . '/log/bpn-api.log', + ], + ], + ]; + + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['ep_theme'] = + \EP\EpTheme\Hooks\Tcemain::class; + + $layout = 'cms/layout/class.tx_cms_layout.php'; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_country'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserCountryPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_region'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserRegionPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_city'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserCityPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_hotel'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserHotelPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_concept'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserConceptPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_product'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserProductPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_booking_link'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserBookingLinkPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['gmap'] = + \EP\EpTheme\Hooks\PageLayoutView\GmapPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['disturber'] = + \EP\EpTheme\Hooks\PageLayoutView\DisturberPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['disrupter'] = + \EP\EpTheme\Hooks\PageLayoutView\DisrupterPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['lineup'] = + \EP\EpTheme\Hooks\PageLayoutView\LineupPreviewRenderer::class; + + $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'] = 'tx_eptheme_slides'; + + $GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['default'] = 'EXT:ep_theme/Configuration/RTE/Default.yaml'; + $GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['ep-reisen'] = 'EXT:ep_theme/Configuration/RTE/Default.yaml'; + $GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['ep-minimal'] = 'EXT:ep_theme/Configuration/RTE/Minimal.yaml'; + + $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['ep'] = ['EP\\EpTheme\\ViewHelpers']; + $GLOBALS['TYPO3_CONF_VARS']['MAIL']['layoutRootPaths'][700] = 'EXT:ep_theme/Resources/Private/Layouts/Email/'; + $GLOBALS['TYPO3_CONF_VARS']['MAIL']['templateRootPaths'][700] = 'EXT:ep_theme/Resources/Private/Templates/Email/'; + $GLOBALS['TYPO3_CONF_VARS']['MAIL']['format'] = 'html'; + + $metaTagManagerRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry::class); + $metaTagManagerRegistry->registerManager( + 'ep_opengraph', + \EP\EpTheme\MetaTag\OpenGraphMetaTagManager::class + ); +}; + +$boot(); +unset($boot); + diff --git a/packages/ep_theme/ext_tables.php b/packages/ep_theme/ext_tables.php new file mode 100644 index 0000000..7e4d164 --- /dev/null +++ b/packages/ep_theme/ext_tables.php @@ -0,0 +1,39 @@ +registerIcon( + 'content-ep_theme-ce', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:ep_theme/Resources/Public/images/logo.svg'] + ); + } + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule( + 'EP.EpTheme', + 'web', + 'management', + 'after:list', + [ + 'Backend' => 'listPages', + ], + [ + 'access' => 'user,group', + 'labels' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_mod.xlf', + 'icon' => 'EXT:ep_products/Resources/Public/Icons/logo.svg' + ] + ); +}); diff --git a/packages/ep_theme/ext_tables.sql b/packages/ep_theme/ext_tables.sql new file mode 100644 index 0000000..98bb4f1 --- /dev/null +++ b/packages/ep_theme/ext_tables.sql @@ -0,0 +1,225 @@ +# noinspection SqlNoDataSourceInspectionForFile +# +# Table structure for table 'tx_eptheme_domain_model_slider' +# +CREATE TABLE tx_eptheme_domain_model_slide +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + headline varchar(255) DEFAULT '' NOT NULL, + subline varchar(255) DEFAULT '' NOT NULL, + button varchar(255) DEFAULT '' NOT NULL, + page_uid varchar(255) DEFAULT '' NOT NULL, + skipass tinyint(4) unsigned DEFAULT '1' NOT NULL, + image int(11) unsigned NOT NULL default '0', + page int(11) unsigned DEFAULT '0', + sorting int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_eptheme_domain_model_disrupter' +# +CREATE TABLE tx_eptheme_domain_model_disrupter +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + headline varchar(255) DEFAULT '' NOT NULL, + layout tinyint(4) unsigned DEFAULT '0' NOT NULL, + text text NOT NULL, + pi_flexform mediumtext NOT NULL, + image int(11) unsigned NOT NULL default '0', + tt_content int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_eptheme_domain_model_lineup' +# +CREATE TABLE tx_eptheme_domain_model_lineup +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + name varchar(255) DEFAULT '' NOT NULL, + description text NOT NULL, + image int(11) unsigned NOT NULL default '0', + facebook_url varchar(255) DEFAULT '' NOT NULL, + twitter_url varchar(255) DEFAULT '' NOT NULL, + instagram_url varchar(255) DEFAULT '' NOT NULL, + youtube_url varchar(255) DEFAULT '' NOT NULL, + soundcloud_url varchar(255) DEFAULT '' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + tt_content int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +CREATE TABLE pages +( + tx_eptheme_context_pacode char(8) DEFAULT '', + tx_eptheme_context_country_code char(2) DEFAULT '', + tx_eptheme_context_country int(11) unsigned DEFAULT '0', + tx_eptheme_context_region int(11) unsigned DEFAULT '0', + tx_eptheme_context_concept int(11) unsigned DEFAULT '0', + tx_eptheme_context_product int(11) unsigned DEFAULT '0', + tx_eptheme_hide_searchbar tinyint(4) unsigned DEFAULT '0' NOT NULL, + tx_eptheme_new_tab tinyint(4) unsigned DEFAULT '0' NOT NULL, + tx_eptheme_bodyclass varchar(255) DEFAULT '' NOT NULL, + tx_eptheme_navitemclass varchar(255) DEFAULT '' NOT NULL, + tx_eptheme_slides int(11) unsigned DEFAULT '0', + tx_eptheme_badge int(11) unsigned DEFAULT '0' +); + +CREATE TABLE tt_content +( + button varchar(255) DEFAULT '' NOT NULL, + tx_eptheme_context_country int(11) unsigned DEFAULT '0', + tx_eptheme_context_region int(11) unsigned DEFAULT '0', + tx_eptheme_context_city int(11) unsigned DEFAULT '0', + tx_eptheme_context_hotel int(11) unsigned DEFAULT '0', + tx_eptheme_context_concept int(11) unsigned DEFAULT '0', + tx_eptheme_context_product int(11) unsigned DEFAULT '0', + tx_eptheme_context_date_from int(11) unsigned DEFAULT '0' NOT NULL, + tx_eptheme_context_date_to int(11) unsigned DEFAULT '0' NOT NULL, + tx_eptheme_lineup int(11) unsigned DEFAULT '0', + tx_eptheme_contact int(11) unsigned DEFAULT '0', + tx_eptheme_disrupter int(11) unsigned DEFAULT '0', + tx_eptheme_background tinyint(4) unsigned DEFAULT '0' NOT NULL +); + +CREATE TABLE tx_eptheme_ttcontent_tx_eptheme_disrupter_mm +( + uid_local int(11) DEFAULT '0' NOT NULL, + uid_foreign int(11) DEFAULT '0' NOT NULL, + sorting int(11) DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +CREATE TABLE tx_eptheme_domain_model_addresscheck +( + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + tstamp int(11) DEFAULT '0' NOT NULL, + crdate int(11) DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + deleted tinyint(3) DEFAULT '0', + hidden tinyint(4) DEFAULT '0' NOT NULL, + + name tinytext, + first_name tinytext, + last_name tinytext, + customer_id varchar(255) DEFAULT '' NOT NULL, + title varchar(255) DEFAULT '' NOT NULL, + email varchar(255) DEFAULT '' NOT NULL, + address tinytext, + city varchar(255) DEFAULT '' NOT NULL, + zip varchar(20) DEFAULT '' NOT NULL, + country varchar(128) DEFAULT '' NOT NULL, + + no_postal tinyint(4) DEFAULT '0' NOT NULL, + no_email tinyint(4) DEFAULT '0' NOT NULL, + address_current tinyint(4) DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(30) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage tinyint(4) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + t3_origuid int(11) DEFAULT '0' NOT NULL, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY pid (pid, email) +);