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 @@
-
- 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 +
++ Vielen Dank für Deine {{ mode === 'booking' ? 'Buchung' : 'Anfrage' }}. +
++ Für folgendes habt ihr euch entschieden: +
+| {{ selectedOption.title }} | -{{ formatCurrency(selectedOption.price) }} | +{{ formatCurrency(calculateOptionPrice(selectedOption)) }} |
|---|---|---|
| {{ formatCurrency(priceBoard) }} | ||
| Kleingruppen-Verpflegungszuschlag | + v-if="(priceBoard.EUR > 0 || priceBoard.CHF > 0) && (pricePax.undersubscription.EUR > 0 || pricePax.undersubscription.CHF > 0)"> +Verpflegungs-Aufschlag für Gruppen unter 50 Personen | {{ formatCurrency(pricePax.undersubscription) }} |
| Gesamtpreis | -{{ formatCurrency(priceTotal) }}
- zzgl. vor Ort zu entrichtender Ortstaxe + |
+ {{ formatCurrency(priceTotal) }}
+ + {{ taxLabel }} |