diff --git a/.ddev/docker-compose.communicate.yaml b/.ddev/docker-compose.communicate.yaml new file mode 100644 index 00000000..6e222918 --- /dev/null +++ b/.ddev/docker-compose.communicate.yaml @@ -0,0 +1,4 @@ +services: + web: + external_links: + - "ddev-router:myep-next.ddev.site" diff --git a/composer.json b/composer.json index fd447415..e1eb7ada 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "helhum/typo3-console": "^6.4", "kigkonsult/icalcreator": "^2.29", "league/csv": "^9.2", + "league/oauth2-client": "^2.8", "league/period": "^4.9", "lochmueller/staticfilecache": "^12.5", "sjbr/sr-freecap": "^2.6", diff --git a/composer.lock b/composer.lock index 05629b20..5f4e8634 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": "f4b99aa9b07e56210c516195545a1d40", + "content-hash": "4f59a07672e06df6976e44c1999be0f2", "packages": [ { "name": "b13/container", @@ -465,26 +465,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "1.4.10 || 2.0.3", + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -504,9 +507,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" }, - "time": "2024-12-07T21:18:45+00:00" + "time": "2025-04-07T20:06:18+00:00" }, { "name": "doctrine/event-manager", @@ -1744,6 +1747,71 @@ ], "time": "2022-01-04T00:13:07+00:00" }, + { + "name": "league/oauth2-client", + "version": "2.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "9df2924ca644736c835fc60466a3a60390d334f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/9df2924ca644736c835fc60466a3a60390d334f9", + "reference": "9df2924ca644736c835fc60466a3a60390d334f9", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "php": "^7.1 || >=8.0.0 <8.5.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.8.1" + }, + "time": "2025-02-26T04:37:30+00:00" + }, { "name": "league/period", "version": "4.12.0", @@ -2161,16 +2229,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.1", + "version": "5.6.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", - "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", "shasum": "" }, "require": { @@ -2219,9 +2287,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" }, - "time": "2024-12-07T09:39:29+00:00" + "time": "2025-04-13T19:20:35+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -4487,7 +4555,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -4546,7 +4614,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -4566,7 +4634,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -4624,7 +4692,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -4644,16 +4712,16 @@ }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78" + "reference": "763d2a91fea5681509ca01acbc1c5e450d127811" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", - "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/763d2a91fea5681509ca01acbc1c5e450d127811", + "reference": "763d2a91fea5681509ca01acbc1c5e450d127811", "shasum": "" }, "require": { @@ -4708,7 +4776,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.32.0" }, "funding": [ { @@ -4724,20 +4792,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-21T18:38:29+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -4791,7 +4859,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.0" }, "funding": [ { @@ -4807,11 +4875,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -4872,7 +4940,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -4892,19 +4960,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -4952,7 +5021,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -4968,7 +5037,7 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php72", @@ -5037,7 +5106,7 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -5093,7 +5162,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" }, "funding": [ { @@ -5113,16 +5182,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -5173,7 +5242,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { @@ -5189,11 +5258,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -5249,7 +5318,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" }, "funding": [ { diff --git a/config/sites/ep-reisen/config.yaml b/config/sites/ep-reisen/config.yaml index 8e140cc3..bfd4764b 100644 --- a/config/sites/ep-reisen/config.yaml +++ b/config/sites/ep-reisen/config.yaml @@ -255,28 +255,25 @@ routeEnhancers: - 2736 routes: - - routePath: '/{travel_info}/{travel_date}' - _controller: 'Travelinfo::index' + routePath: '/{travel_info}' + _controller: 'Travelinfo::travelInfo' _arguments: - travel_info: travelinfo - travel_date: travelDate + travel_info: travelInfo - routePath: '/{travel_code}' _controller: 'Travelinfo::travelCode' _arguments: travel_code: travelCode - defaultController: 'Travelinfo::index' + defaultController: 'Travelinfo::travelInfo' + requirements: + travel_code: '[A-Z]+\d{6}$' aspects: travel_info: type: PersistedAliasMapper tableName: tx_epproducts_domain_model_travelinfo routeFieldName: path_segment - travel_date: - type: PassthroughMapper travel_code: type: PassthroughMapper - requirements: - travel_date: '\d{4}\-\d{2}\-\d{2}|\d{2}\d{2}\d{4}' PageTypeSuffix: type: PageType default: / diff --git a/public/typo3conf/ext/ep_products/Classes/Controller/AjaxGroupsPriceController.php b/public/typo3conf/ext/ep_products/Classes/Controller/AjaxGroupsPriceController.php index 9e22f84b..31514b51 100644 --- a/public/typo3conf/ext/ep_products/Classes/Controller/AjaxGroupsPriceController.php +++ b/public/typo3conf/ext/ep_products/Classes/Controller/AjaxGroupsPriceController.php @@ -28,6 +28,7 @@ namespace EP\EpProducts\Controller; use EP\EpProducts\Domain\Model\Dto\GroupsPriceInquiry; use EP\EpProducts\Domain\Model\GroupsPriceBoard; +use EP\EpProducts\Domain\Model\GroupsPriceOption; use EP\EpProducts\Domain\Model\Hotel; use EP\EpProducts\Domain\Repository\GroupsPriceOptionRepository; use EP\EpProducts\Service\EmailService; @@ -69,6 +70,9 @@ class AjaxGroupsPriceController extends ActionController public function processFormAction(Hotel $hotel, GroupsPriceInquiry $groupsPriceInquiry) { $mode = $groupsPriceInquiry->getMode(); + $country = $hotel->getCountry()->getCode(); + $effectivePax = $groupsPriceInquiry->getEffectivePax(); + $nights = $groupsPriceInquiry->getNights(); if (false === in_array($mode, [GroupsPriceInquiry::MODE_BOOKING, GroupsPriceInquiry::MODE_INQUIRY])) { $mode = GroupsPriceInquiry::MODE_BOOKING; @@ -86,10 +90,25 @@ class AjaxGroupsPriceController extends ActionController 'templateName' => 'GroupsPrice', ]; + $optionPrice = function (GroupsPriceOption $option) use ($country, $effectivePax, $nights) { + $price = 'CH' === $country ? $option->getPriceChf() : $option->getPrice(); + $currency = 'CH' === $country ? 'CHF' : '€'; + + if (GroupsPriceOption::TYPE_PER_PAX === $option->getType()) { + $price = $price * $effectivePax; + } elseif (GroupsPriceOption::TYPE_PER_NIGHT === $option->getType()) { + $price = $price * $nights; + } elseif (GroupsPriceOption::TYPE_PER_PAX_AND_NIGHT === $option->getType()) { + $price = $price * $nights * $effectivePax; + } + + return $price.' '.$currency; + }; + try { $options = $this->groupsPriceOptionRepository->findByUids($groupsPriceInquiry->getOptions()); - $selectedOptions = array_map(function ($option) { - return $option->getTitle(); + $selectedOptions = array_map(function ($option) use ($optionPrice) { + return $option->getTitle().': '.$optionPrice($option); }, $options); } catch (InvalidQueryException $e) { @@ -101,13 +120,20 @@ class AjaxGroupsPriceController extends ActionController 'hotel' => $hotel, 'name' => $groupsPriceInquiry->getName(), 'group' => $groupsPriceInquiry->getGroup(), - 'address' => $groupsPriceInquiry->getAddress(), + 'street' => $groupsPriceInquiry->getStreet(), + 'postcode' => $groupsPriceInquiry->getPostcode(), + 'city' => $groupsPriceInquiry->getCity(), 'email' => $groupsPriceInquiry->getEmail(), 'phone' => $groupsPriceInquiry->getPhone(), 'remarks' => $groupsPriceInquiry->getRemarks(), 'dateFrom' => $groupsPriceInquiry->getDateFrom(), 'dateTo' => $groupsPriceInquiry->getDateTo(), + 'nights' => $groupsPriceInquiry->getNights(), 'pax' => $groupsPriceInquiry->getPax(), + 'children' => $groupsPriceInquiry->getChildren(), + 'minors' => $groupsPriceInquiry->getMinors(), + 'adolescents' => $groupsPriceInquiry->getAdolescents(), + 'adolescentsAge' => $hotel->getGroupsPriceAdolescentsAge(), 'options' => $selectedOptions, 'board' => $groupsPriceInquiry->getBoard(), 'summary' => $groupsPriceInquiry->getSummary(), @@ -124,8 +150,7 @@ class AjaxGroupsPriceController extends ActionController protected function errorAction() { $formErrors = []; if ($this->arguments->validate()->hasErrors()) { - foreach ($this->arguments->validate()->getFlattenedErrors() as $key => $errors) - { + foreach ($this->arguments->validate()->getFlattenedErrors() as $key => $errors) { $parts = explode('.', $key); $fieldName = array_pop($parts); $errorsRaw = []; diff --git a/public/typo3conf/ext/ep_products/Classes/Controller/GroupsPriceController.php b/public/typo3conf/ext/ep_products/Classes/Controller/GroupsPriceController.php index b2ee2d24..9b78065f 100644 --- a/public/typo3conf/ext/ep_products/Classes/Controller/GroupsPriceController.php +++ b/public/typo3conf/ext/ep_products/Classes/Controller/GroupsPriceController.php @@ -52,15 +52,16 @@ class GroupsPriceController extends ActionController $this->view->assign('hotel', $hotel); $this->view->assign('runningCostsEUR', $this->settings['runningCostsEUR']); $this->view->assign('runningCostsCHF', $this->settings['runningCostsCHF']); - $this->view->assign('undersubscriptionEUR', $this->settings['undersubscriptionEUR']); - $this->view->assign('undersubscriptionCHF', $this->settings['undersubscriptionCHF']); - $this->view->assign('undersubscriptionExtEUR', $this->settings['undersubscriptionExtEUR']); - $this->view->assign('undersubscriptionExtCHF', $this->settings['undersubscriptionExtCHF']); + $this->view->assign('undersubscription30EUR', $this->settings['undersubscription30EUR']); + $this->view->assign('undersubscription30CHF', $this->settings['undersubscription30CHF']); + $this->view->assign('undersubscription40EUR', $this->settings['undersubscription40EUR']); + $this->view->assign('undersubscription40CHF', $this->settings['undersubscription40CHF']); $this->view->assign('countryCode', $hotel->getCountry()->getCode()); $this->view->assign('configs', json_encode(array_values($configs), JSON_HEX_APOS)); $this->view->assign('boards', json_encode($hotel->getGroupsPriceBoards()->toArray(), JSON_HEX_APOS)); $this->view->assign('options', json_encode($hotel->getGroupsPriceOptions()->toArray(), JSON_HEX_APOS)); $this->view->assign('mainSeasonFrom', $mainSeasonFrom); $this->view->assign('mainSeasonTo', $mainSeasonTo); + $this->view->assign('adolescentsAge', $hotel->getGroupsPriceAdolescentsAge() ?? 0); } } diff --git a/public/typo3conf/ext/ep_products/Classes/Controller/TravelinfoController.php b/public/typo3conf/ext/ep_products/Classes/Controller/TravelinfoController.php index 582f3866..2d816237 100644 --- a/public/typo3conf/ext/ep_products/Classes/Controller/TravelinfoController.php +++ b/public/typo3conf/ext/ep_products/Classes/Controller/TravelinfoController.php @@ -2,11 +2,10 @@ namespace EP\EpProducts\Controller; -use EP\EpProducts\Domain\Model\Date; use EP\EpProducts\Domain\Model\Travelinfo; -use EP\EpProducts\Domain\Repository\DateRepository; -use EP\EpProducts\Domain\Repository\ProductRepository; use EP\EpProducts\Domain\Repository\TravelinfoRepository; +use EP\EpProducts\MyEP\ApiClient; +use EP\EpProducts\MyEP\ApiException; use TYPO3\CMS\Core\Http\ImmediateResponseException; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; @@ -15,59 +14,34 @@ use TYPO3\CMS\Frontend\Controller\ErrorController; class TravelinfoController extends ActionController { /** - * @var DateRepository + * @var ApiClient */ - private $dateRepository; + private $apiClient; /** * @var TravelinfoRepository */ private $travelinfoRepository; - /** - * @var ProductRepository - */ - private $productRepository; - - public function __construct( - DateRepository $dateRepository, - TravelinfoRepository $travelinfoRepository, - ProductRepository $productRepository - ) { - $this->dateRepository = $dateRepository; + public function __construct(ApiClient $apiClient, TravelinfoRepository $travelinfoRepository) + { + $this->apiClient = $apiClient; $this->travelinfoRepository = $travelinfoRepository; - $this->productRepository = $productRepository; } public function travelCodeAction(string $travelCode) { - $travelCode = strtoupper($travelCode); - $travelCodeDate = str_replace('-', '/', $travelCode); + // find specific travel info by code including travel date first + $travelInfo = $this->travelinfoRepository->findOneByTravelCode($travelCode); - /** @var Date $date */ - $date = $this->dateRepository->findOneByCode($travelCodeDate); - - if (null === $date) { - $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction( - $GLOBALS['TYPO3_REQUEST'], - 'Termin nicht gefunden' - ); - throw new ImmediateResponseException($response, 1731071856); + // find general travel info by code without travel date as fallback + if (null === $travelInfo) { + $travelCodeBase = substr($travelCode, 0, -6); + $travelInfo = $this->travelinfoRepository->findOneByTravelCode($travelCodeBase); } - $product = $date->getProduct(); - - if (null === $product) { - $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction( - $GLOBALS['TYPO3_REQUEST'], - 'Produkt nicht gefunden' - ); - throw new ImmediateResponseException($response, 1731071856); - } - - $travelinfo = $this->travelinfoRepository->findOneByProduct($product); - - if (null === $travelinfo) { + // 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' @@ -75,22 +49,49 @@ class TravelinfoController extends ActionController throw new ImmediateResponseException($response, 1731071856); } - $this->view->assign('travelinfo', $travelinfo); - $this->view->assign('date', $date); + $this->handleRequest($travelCode, $travelInfo); } - public function indexAction(Travelinfo $travelinfo, string $travelDate) + public function travelInfoAction(Travelinfo $travelInfo) { - $product = $travelinfo->getProduct(); + $travelCode = $travelInfo->getTravelCode(); - if (preg_match('/^\d{2}\d{2}\d{4}$/', $travelDate)) { - $travelDate = (\DateTimeImmutable::createFromFormat('dmY', $travelDate))->format('Y-m-d'); + $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 = []; } - /** @var Date $date */ - $date = $this->dateRepository->findForProductAndDate($product, $travelDate)->getFirst(); + // extract courses from additional services + $courses = array_filter($travelData['additionalServices'] ?? [], function ($service) { + return 'KUR' === $service['subType']; + }); - $this->view->assign('travelinfo', $travelinfo); - $this->view->assign('date', $date); + // extract board services from additional services + $board = array_filter($travelData['additionalServices'] ?? [], function ($service) { + return 'VPF' === $service['subType']; + }); + + // extract bus departure time if applicable + $departureDayTime = null; + $service = array_filter($travelData['transportationServices'] ?? [], function ($service) { + return 'BUS' === $service['subType'] && 'RUECK' === $service['direction']; + }); + if (null !== $service[0] ?? null) { + $departureDayTime = $service[0]['dayTime']; + } + + $this->view->assign('travelInfo', $travelInfo); + $this->view->assign('travelData', $travelData); + $this->view->assign('courses', $courses); + $this->view->assign('board', $board); + $this->view->assign('departureDayTime', $departureDayTime); } } diff --git a/public/typo3conf/ext/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php b/public/typo3conf/ext/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php index 99bbfdb7..141ce4e0 100644 --- a/public/typo3conf/ext/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php +++ b/public/typo3conf/ext/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php @@ -60,7 +60,19 @@ class GroupsPriceInquiry * @var string * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") */ - protected $address; + protected $street; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $postcode; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $city; /** * @var string @@ -92,11 +104,31 @@ class GroupsPriceInquiry */ protected $dateTo; + /** + * @var string + */ + protected $nights; + /** * @var string */ protected $pax; + /** + * @var string + */ + protected $children; + + /** + * @var string + */ + protected $minors; + + /** + * @var string + */ + protected $adolescents; + /** * @var array */ @@ -104,6 +136,7 @@ class GroupsPriceInquiry /** * @var GroupsPriceBoard + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") */ protected $board; @@ -186,21 +219,53 @@ class GroupsPriceInquiry /** * @return string */ - public function getAddress() + public function getStreet() { - return $this->address; + return $this->street; } /** - * @param string $address + * @param string $street */ - public function setAddress($address) + public function setStreet($street) { - $this->address = $address; + $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 */ @@ -281,6 +346,22 @@ class GroupsPriceInquiry $this->dateTo = $dateTo; } + /** + * @return string + */ + public function getNights() + { + return $this->nights; + } + + /** + * @param string $nights + */ + public function setNights($nights) + { + $this->nights = $nights; + } + /** * @return string */ @@ -297,6 +378,62 @@ class GroupsPriceInquiry $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, $this->getPax() - $this->getChildren()); + } + /** * @return array */ diff --git a/public/typo3conf/ext/ep_products/Classes/Domain/Model/Hotel.php b/public/typo3conf/ext/ep_products/Classes/Domain/Model/Hotel.php index e3c8aad2..efdab7c9 100644 --- a/public/typo3conf/ext/ep_products/Classes/Domain/Model/Hotel.php +++ b/public/typo3conf/ext/ep_products/Classes/Domain/Model/Hotel.php @@ -265,6 +265,16 @@ class Hotel extends AbstractEntity implements TeaserInterface */ protected $groupsPriceBoards; + /** + * @var int + */ + protected $groupsPriceAdolescentsAge; + + /** + * @var string + */ + protected $groupsPriceTaxLabel; + /** * @var \DateTime */ @@ -1054,6 +1064,26 @@ class Hotel extends AbstractEntity implements TeaserInterface $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; diff --git a/public/typo3conf/ext/ep_products/Classes/Domain/Repository/ProductRepository.php b/public/typo3conf/ext/ep_products/Classes/Domain/Repository/ProductRepository.php index 3753dfa4..4be07071 100644 --- a/public/typo3conf/ext/ep_products/Classes/Domain/Repository/ProductRepository.php +++ b/public/typo3conf/ext/ep_products/Classes/Domain/Repository/ProductRepository.php @@ -191,7 +191,7 @@ class ProductRepository extends AbstractRepository ; } - public function getPricetable(Product $product, Hotel $hotel, array $filterSettings, Date $date = null, bool $sortByRoom = true): array + public function getPricetable(Product $product, Hotel $hotel, array $filterSettings, Date $date = null): array { $qb = $this->getDbConnection()->createQueryBuilder(); @@ -207,18 +207,11 @@ class ProductRepository extends AbstractRepository ) ->from('tx_epproducts_domain_model_date', 'date') ->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'date.uid = room.date') - ->orderBy('room.available', 'DESC') + ->orderBy('room.pax', 'ASC') + ->addOrderBy('room.price', 'ASC') + ->addOrderBy('room.available', 'DESC') ; - if (true === $sortByRoom) { - $qb->addOrderBy('room.name', 'ASC'); - } else { - $qb - ->addOrderBy('room.pax', 'ASC') - ->addOrderBy('room.price', 'ASC') - ; - } - $qb ->where('date.date_start >= NOW()') ->andWhere($qb->expr()->eq( @@ -365,7 +358,8 @@ class ProductRepository extends AbstractRepository '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.pax as roomPax', 'room.price as roomPrice', 'room.available as available', + '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' ) diff --git a/public/typo3conf/ext/ep_products/Classes/MyEP/ApiClient.php b/public/typo3conf/ext/ep_products/Classes/MyEP/ApiClient.php new file mode 100644 index 00000000..d1dae47b --- /dev/null +++ b/public/typo3conf/ext/ep_products/Classes/MyEP/ApiClient.php @@ -0,0 +1,149 @@ +getHttpClient(); + + try { + $response = $httpClient->request('GET', 'last-update'); + } catch (TransportExceptionInterface $e) { + throw new ApiException($e->getMessage()); + } + + try { + if (200 !== $response->getStatusCode()) { + throw new ApiException($response->getStatusCode()); + } + + $data = $response->toArray(false); + + if (null === $data['timestamp'] ?? null) { + return null; + } + + return new \DateTimeImmutable($data['timestamp']); + } catch (\Throwable $e) { + throw new ApiException($e->getMessage()); + } + } + + /** + * @throws ApiException + */ + public function getPickups(): array + { + $httpClient = $this->getHttpClient(); + + try { + $response = $httpClient->request('GET', 'pickups'); + } catch (TransportExceptionInterface $e) { + throw new ApiException($e->getMessage()); + } + + try { + if (200 !== $response->getStatusCode()) { + throw new ApiException($response->getStatusCode()); + } + + return $response->toArray(false); + } catch (\Throwable $e) { + throw new ApiException($e->getMessage()); + } + } + + /** + * @throws ApiException + */ + public function getTravels(): array + { + $httpClient = $this->getHttpClient(); + + try { + $response = $httpClient->request('GET', 'travels'); + } catch (TransportExceptionInterface $e) { + throw new ApiException($e->getMessage()); + } + + try { + if (200 !== $response->getStatusCode()) { + throw new ApiException($response->getStatusCode()); + } + + return $response->toArray(false); + } catch (\Throwable $e) { + throw new ApiException($e->getMessage()); + } + } + + /** + * @throws ApiException + */ + public function getTravel(string $productCode): array + { + $httpClient = $this->getHttpClient(); + + try { + $response = $httpClient->request('GET', 'travels/' . $productCode); + } catch (TransportExceptionInterface $e) { + throw new ApiException($e->getMessage()); + } + + try { + if (200 !== $response->getStatusCode()) { + throw new ApiException($response->getStatusCode()); + } + + return $response->toArray(false); + } catch (\Throwable $e) { + throw new ApiException($e->getMessage()); + } + } + + private function getHttpClient(): HttpClientInterface + { + $config = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); + + if (null === static::$accessToken || true === static::$accessToken->hasExpired()) { + static::$accessToken = $this + ->getProvider($config) + ->getAccessToken('client_credentials') + ; + } + + return HttpClient::createForBaseUri($config['myEpApiBaseUrl'], [ + 'auth_bearer' => static::$accessToken->getToken(), + ]); + } + + private function getProvider(array $config): AbstractProvider + { + return new GenericProvider([ + 'clientId' => $config['myEpApiClientId'], + 'clientSecret' => $config['myEpApiClientSecret'], + 'redirectUri' => null, + 'urlAuthorize' => $config['myEpApiUrlAuthorize'], + 'urlAccessToken' => $config['myEpApiUrlToken'], + 'urlResourceOwnerDetails' => null, + 'scopes' => 'api', + ]); + } +} diff --git a/public/typo3conf/ext/ep_products/Classes/MyEP/ApiException.php b/public/typo3conf/ext/ep_products/Classes/MyEP/ApiException.php new file mode 100644 index 00000000..b59a70c7 --- /dev/null +++ b/public/typo3conf/ext/ep_products/Classes/MyEP/ApiException.php @@ -0,0 +1,7 @@ +filterService->getNormalizedFilterSettings(); @@ -118,7 +117,7 @@ class DateService implements SingletonInterface $nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true); return $this->preprocessPriceTable([ - 'priceTableData' => $this->productRepository->getPricetable($product, $hotel, $filterSettings, $date, $sortByRoom), + 'priceTableData' => $this->productRepository->getPricetable($product, $hotel, $filterSettings, $date), 'template' => $template, 'paCode' => $paCode, 'isDayTrip' => $product->isDaytrip(), @@ -188,7 +187,11 @@ class DateService implements SingletonInterface $resolver->setDefault('isDayTrip', false); $resolver->setDefault('showBookingButton', true); $resolvedOptions = $resolver->resolve($options); - $priceTable = []; + + $priceTable = [ + 'available' => [], + 'unavailable' => [], + ]; $priceTableData = $resolvedOptions['priceTableData']; $template = $resolvedOptions['template']; @@ -210,7 +213,10 @@ class DateService implements SingletonInterface 'isDayTrip' => $isDayTrip, ]); $optionalServices = $this->preprocessOptionalServices(unserialize($row['roomOptionalServices'], ['allowed_classes' => false])); - $priceTable[] = [ + + $key = $row['roomAvailable'] && $row['roomPrice'] ? 'available' : 'unavailable'; + + $priceTable[$key][] = [ 'dateStart' => $dateStart->format('d.m.Y'), 'dateEnd' => $dateEnd->format('d.m.Y'), 'season' => $row['season'], @@ -378,6 +384,8 @@ class DateService implements SingletonInterface } $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 @@ -385,7 +393,7 @@ class DateService implements SingletonInterface $entry[$roomPax] = [ 'price' => $row['roomPrice'], 'available' => $row['available'] && $row['roomPrice'], - + 'undersubscription' => $isUndersubscription, ] ; } else { $lastEntry = $entry[$roomPax]; @@ -393,6 +401,7 @@ class DateService implements SingletonInterface $entry[$roomPax] = [ 'price' => $row['roomPrice'], 'available' => $row['available'] && $row['roomPrice'], + 'undersubscription' => $isUndersubscription, ]; } } diff --git a/public/typo3conf/ext/ep_products/Classes/Service/SearchResultService.php b/public/typo3conf/ext/ep_products/Classes/Service/SearchResultService.php index 0045a31c..7aeddda3 100644 --- a/public/typo3conf/ext/ep_products/Classes/Service/SearchResultService.php +++ b/public/typo3conf/ext/ep_products/Classes/Service/SearchResultService.php @@ -69,7 +69,7 @@ class SearchResultService implements SingletonInterface 'destinationUid' => null, 'destinationType' => null, 'nights' => null, - 'pax' => 1, + 'pax' => 0, 'pageUid' => null, ]); diff --git a/public/typo3conf/ext/ep_products/Configuration/FlexForms/flexform_groups_price_calculator.xml b/public/typo3conf/ext/ep_products/Configuration/FlexForms/flexform_groups_price_calculator.xml index 49aed66c..f8c607c3 100644 --- a/public/typo3conf/ext/ep_products/Configuration/FlexForms/flexform_groups_price_calculator.xml +++ b/public/typo3conf/ext/ep_products/Configuration/FlexForms/flexform_groups_price_calculator.xml @@ -32,7 +32,7 @@ - + 0 @@ -42,8 +42,8 @@ double2,required - - + + 0 @@ -53,8 +53,8 @@ double2,required - - + + 0 @@ -64,8 +64,8 @@ double2,required - - + + 0 @@ -75,7 +75,7 @@ double2,required - + diff --git a/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php b/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php index 8a72f8c9..9359f533 100644 --- a/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php +++ b/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php @@ -44,6 +44,7 @@ return [ ['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' => [ diff --git a/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php b/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php index 705e2ed7..7e551035 100644 --- a/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php +++ b/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php @@ -45,6 +45,7 @@ return [ ['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' => [ diff --git a/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php b/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php index 03fb15f0..cacc0920 100644 --- a/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php +++ b/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php @@ -30,7 +30,8 @@ return [ --div--;Texte, teaser, description, features, room_types, additional_information, --div--;Verknüpfungen, facts, teamer, additional_codes, --div--;Bilder/Dateien, teaser_images, header_images, images, reseller_images, - --div--;Gruppenhaus Preise, --palette--;;season, groups_price_configs, groups_price_boards, groups_price_options', + --div--;Gruppenhaus Preise, --palette--;;season,--palette--;;others, groups_price_configs, + groups_price_boards, groups_price_options', ], ], 'palettes' => [ @@ -41,6 +42,7 @@ return [ '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'] ], 'columns' => [ @@ -639,6 +641,7 @@ return [ 'type' => 'inline', 'foreign_table' => 'tx_epproducts_domain_model_groupspriceconfig', 'foreign_field' => 'hotel', + 'foreign_sortby' => 'sorting', 'maxitems' => 99, 'minitems' => 0, 'appearance' => [ @@ -691,6 +694,25 @@ return [ ], ], ], + 'groups_price_tax_label' => [ + 'exclude' => 0, + 'label' => 'Text Ortstaxe', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + 'default' => 'zzgl. vor Ort 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', diff --git a/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php b/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php index 2b9258cb..a90025c6 100644 --- a/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php +++ b/public/typo3conf/ext/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php @@ -27,7 +27,7 @@ return [ ], ], 'palettes' => [ - 'basics' => ['showitem' => 'title,path_segment,--linebreak--,product,hide_address,--linebreak--,links, + '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, @@ -191,32 +191,13 @@ return [ $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] ), ], - 'product' => [ + 'travel_code' => [ 'exclude' => false, - 'label' => 'Produkt', + 'label' => 'Reisecode', 'config' => [ - 'type' => 'group', - 'internal_type' => 'db', - 'allowed' => 'tx_epproducts_domain_model_product', - 'minitems' => 1, - 'maxitems' => 1, - 'size' => 1, - 'fieldControl' => [ - 'elementBrowser' => [ - 'disabled' => true, - ], - ], - 'fieldWizard' => [ - 'recordsOverview' => [ - 'disabled' => true, - ], - ], - 'suggestOptions' => [ - 'default' => [ - 'additionalSearchFields' => 'code,name,keywords', - 'orderBy' => 'code', - ], - ], + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' ], ], 'hide_address' => [ diff --git a/public/typo3conf/ext/ep_products/ext_conf_template.txt b/public/typo3conf/ext/ep_products/ext_conf_template.txt index 6afb2235..04fb2ed8 100644 --- a/public/typo3conf/ext/ep_products/ext_conf_template.txt +++ b/public/typo3conf/ext/ep_products/ext_conf_template.txt @@ -1,3 +1,18 @@ -# customsubcategory=100=General -# cat=epproducts/100/100; type=int; label=Searchresult PID +# cat=General; type=int; label=Searchresult PID searchPageUid = 0 + +# 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 = + diff --git a/public/typo3conf/ext/ep_products/ext_localconf.php b/public/typo3conf/ext/ep_products/ext_localconf.php index c35b3d7d..b46c17a7 100644 --- a/public/typo3conf/ext/ep_products/ext_localconf.php +++ b/public/typo3conf/ext/ep_products/ext_localconf.php @@ -252,7 +252,7 @@ $boot = function () { 'EP.ep_products', 'travelinfo', [ - 'Travelinfo' => 'index,travelCode', + 'Travelinfo' => 'travelInfo,travelCode', ] ); diff --git a/public/typo3conf/ext/ep_products/ext_tables.sql b/public/typo3conf/ext/ep_products/ext_tables.sql index 3b59b4a5..2c71cbf4 100644 --- a/public/typo3conf/ext/ep_products/ext_tables.sql +++ b/public/typo3conf/ext/ep_products/ext_tables.sql @@ -213,72 +213,74 @@ CREATE TABLE tx_epproducts_domain_model_date CREATE TABLE tx_epproducts_domain_model_hotel ( - uid int(11) NOT NULL auto_increment, - pid int(11) DEFAULT '0' NOT NULL, + 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, - 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', - main_season_from date DEFAULT NULL, - main_season_to date DEFAULT 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, + 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, - 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, + 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, + 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, + 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), diff --git a/public/typo3conf/ext/ep_theme/Classes/ViewHelpers/IconViewHelper.php b/public/typo3conf/ext/ep_theme/Classes/ViewHelpers/IconViewHelper.php index 8e2a8826..0bcb8ae5 100644 --- a/public/typo3conf/ext/ep_theme/Classes/ViewHelpers/IconViewHelper.php +++ b/public/typo3conf/ext/ep_theme/Classes/ViewHelpers/IconViewHelper.php @@ -29,6 +29,7 @@ class IconViewHelper extends AbstractTagBasedViewHelper $content = sprintf('', $imageUrl, $this->arguments['icon']); $this->tag->setContent($content); + $this->tag->addAttributes(['aria-hidden' => 'true']); return $this->tag->render(); } diff --git a/public/typo3conf/ext/ep_theme/Classes/ViewHelpers/LazyLoadImageViewHelper.php b/public/typo3conf/ext/ep_theme/Classes/ViewHelpers/LazyLoadImageViewHelper.php index b769a935..35cadc37 100644 --- a/public/typo3conf/ext/ep_theme/Classes/ViewHelpers/LazyLoadImageViewHelper.php +++ b/public/typo3conf/ext/ep_theme/Classes/ViewHelpers/LazyLoadImageViewHelper.php @@ -41,6 +41,8 @@ class LazyLoadImageViewHelper extends ImageViewHelper $classItems[] = 'lazyload'; $classes = implode(' ', array_unique($classItems)); $this->tag->addAttribute('class', $classes); + // ensure a11y compliance + $this->tag->removeAttribute('title'); return $this->tag->render(); } diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/images/icons_sprite.svg b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/images/icons_sprite.svg index 2c26f670..3547da80 100644 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/images/icons_sprite.svg +++ b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/images/icons_sprite.svg @@ -1,4 +1,4 @@ - +
- - -

- Bitte zuerst einen Zeitraum wählen -

+
+ + +

+ Bitte zuerst einen Zeitraum wählen +

+
+
+ + +

+ Bitte zuerst einen Zeitraum wählen +

+

+ Kinder werden nur bei Strom- und Abfallgebühren berücksichtigt. +

+
+
+ + +

+ Bitte zuerst einen Zeitraum wählen +

+
+
+ + +

+ Bitte zuerst einen Zeitraum wählen +

+
+
+ *Die Kosten für Essen und Kurtaxe werden wir entsprechend der Altersstruktur der Gäste in der Rechnung + anpassen. +
@@ -51,27 +107,38 @@
+
Erst ab 30 Personen buchbar.
+
+

+ Vielen Dank für Deine {{ mode === 'booking' ? 'Buchung' : 'Anfrage' }}. +

+

+ Für folgendes habt ihr euch entschieden: +

+
Preise
-
+
@@ -95,7 +162,7 @@ - + @@ -103,8 +170,8 @@ - + v-if="(priceBoard.EUR > 0 || priceBoard.CHF > 0) && (pricePax.undersubscription.EUR > 0 || pricePax.undersubscription.CHF > 0)"> + - @@ -125,6 +194,15 @@ Bitte zuerst einen Zeitraum wählen

+
+ Bitte beachtet, dass die Buchung bei uns eingegangen, aber noch nicht bestätigt ist. + Wir behalten uns vor, die Buchungen auf Verfügbarkeit und Länge des gewünschten Aufenthalts zu prüfen. + Erst wenn wir euch die Buchung per Mail bestätigt haben, wird sie bindend. +
+
+ Die Anfrage ist bei uns eingegangen und wird schnellstmöglich bearbeitet. Wir melden uns telefonisch + oder per Mail bei euch. +
@@ -144,17 +222,38 @@ v-show="formErrors.name" v-html="formErrors.name">
-
+
-
{{ selectedOption.title }}{{ formatCurrency(selectedOption.price) }}{{ formatCurrency(calculateOptionPrice(selectedOption)) }}
{{ formatCurrency(priceBoard) }}
Kleingruppen-VerpflegungszuschlagVerpflegungs-Aufschlag für Gruppen unter 50 Personen {{ formatCurrency(pricePax.undersubscription) }}
Gesamtpreis{{ formatCurrency(priceTotal) }} -
zzgl. vor Ort zu entrichtender Ortstaxe +
+ {{ formatCurrency(priceTotal) }} +
+ {{ taxLabel }}