feat: integrate travelinfo with myep API to fetch travel data
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
services:
|
||||||
|
web:
|
||||||
|
external_links:
|
||||||
|
- "ddev-router:myep-next.ddev.site"
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
"helhum/typo3-console": "^6.4",
|
"helhum/typo3-console": "^6.4",
|
||||||
"kigkonsult/icalcreator": "^2.29",
|
"kigkonsult/icalcreator": "^2.29",
|
||||||
"league/csv": "^9.2",
|
"league/csv": "^9.2",
|
||||||
|
"league/oauth2-client": "^2.8",
|
||||||
"league/period": "^4.9",
|
"league/period": "^4.9",
|
||||||
"lochmueller/staticfilecache": "^12.5",
|
"lochmueller/staticfilecache": "^12.5",
|
||||||
"sjbr/sr-freecap": "^2.6",
|
"sjbr/sr-freecap": "^2.6",
|
||||||
|
|||||||
Generated
+66
-1
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f4b99aa9b07e56210c516195545a1d40",
|
"content-hash": "4f59a07672e06df6976e44c1999be0f2",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "b13/container",
|
"name": "b13/container",
|
||||||
@@ -1747,6 +1747,71 @@
|
|||||||
],
|
],
|
||||||
"time": "2022-01-04T00:13:07+00:00"
|
"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": "[email protected]",
|
||||||
|
"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",
|
"name": "league/period",
|
||||||
"version": "4.12.0",
|
"version": "4.12.0",
|
||||||
|
|||||||
@@ -255,28 +255,25 @@ routeEnhancers:
|
|||||||
- 2736
|
- 2736
|
||||||
routes:
|
routes:
|
||||||
-
|
-
|
||||||
routePath: '/{travel_info}/{travel_date}'
|
routePath: '/{travel_info}'
|
||||||
_controller: 'Travelinfo::index'
|
_controller: 'Travelinfo::travelInfo'
|
||||||
_arguments:
|
_arguments:
|
||||||
travel_info: travelinfo
|
travel_info: travelInfo
|
||||||
travel_date: travelDate
|
|
||||||
-
|
-
|
||||||
routePath: '/{travel_code}'
|
routePath: '/{travel_code}'
|
||||||
_controller: 'Travelinfo::travelCode'
|
_controller: 'Travelinfo::travelCode'
|
||||||
_arguments:
|
_arguments:
|
||||||
travel_code: travelCode
|
travel_code: travelCode
|
||||||
defaultController: 'Travelinfo::index'
|
defaultController: 'Travelinfo::travelInfo'
|
||||||
|
requirements:
|
||||||
|
travel_code: '[A-Z]+\d{6}$'
|
||||||
aspects:
|
aspects:
|
||||||
travel_info:
|
travel_info:
|
||||||
type: PersistedAliasMapper
|
type: PersistedAliasMapper
|
||||||
tableName: tx_epproducts_domain_model_travelinfo
|
tableName: tx_epproducts_domain_model_travelinfo
|
||||||
routeFieldName: path_segment
|
routeFieldName: path_segment
|
||||||
travel_date:
|
|
||||||
type: PassthroughMapper
|
|
||||||
travel_code:
|
travel_code:
|
||||||
type: PassthroughMapper
|
type: PassthroughMapper
|
||||||
requirements:
|
|
||||||
travel_date: '\d{4}\-\d{2}\-\d{2}|\d{2}\d{2}\d{4}'
|
|
||||||
PageTypeSuffix:
|
PageTypeSuffix:
|
||||||
type: PageType
|
type: PageType
|
||||||
default: /
|
default: /
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace EP\EpProducts\Controller;
|
namespace EP\EpProducts\Controller;
|
||||||
|
|
||||||
use EP\EpProducts\Domain\Model\Date;
|
|
||||||
use EP\EpProducts\Domain\Model\Travelinfo;
|
use EP\EpProducts\Domain\Model\Travelinfo;
|
||||||
use EP\EpProducts\Domain\Repository\DateRepository;
|
|
||||||
use EP\EpProducts\Domain\Repository\TravelinfoRepository;
|
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\Http\ImmediateResponseException;
|
||||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||||
@@ -14,52 +14,34 @@ use TYPO3\CMS\Frontend\Controller\ErrorController;
|
|||||||
class TravelinfoController extends ActionController
|
class TravelinfoController extends ActionController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var DateRepository
|
* @var ApiClient
|
||||||
*/
|
*/
|
||||||
private $dateRepository;
|
private $apiClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TravelinfoRepository
|
* @var TravelinfoRepository
|
||||||
*/
|
*/
|
||||||
private $travelinfoRepository;
|
private $travelinfoRepository;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(ApiClient $apiClient, TravelinfoRepository $travelinfoRepository)
|
||||||
DateRepository $dateRepository,
|
{
|
||||||
TravelinfoRepository $travelinfoRepository
|
$this->apiClient = $apiClient;
|
||||||
) {
|
|
||||||
$this->dateRepository = $dateRepository;
|
|
||||||
$this->travelinfoRepository = $travelinfoRepository;
|
$this->travelinfoRepository = $travelinfoRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function travelCodeAction(string $travelCode)
|
public function travelCodeAction(string $travelCode)
|
||||||
{
|
{
|
||||||
$travelCode = strtoupper($travelCode);
|
// find specific travel info by code including travel date first
|
||||||
$travelCodeDate = str_replace('-', '/', $travelCode);
|
$travelInfo = $this->travelinfoRepository->findOneByTravelCode($travelCode);
|
||||||
|
|
||||||
/** @var Date $date */
|
// find general travel info by code without travel date as fallback
|
||||||
$date = $this->dateRepository->findOneByCode($travelCodeDate);
|
if (null === $travelInfo) {
|
||||||
|
$travelCodeBase = substr($travelCode, 0, -6);
|
||||||
if (null === $date) {
|
$travelInfo = $this->travelinfoRepository->findOneByTravelCode($travelCodeBase);
|
||||||
$response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
|
||||||
$GLOBALS['TYPO3_REQUEST'],
|
|
||||||
'Termin nicht gefunden'
|
|
||||||
);
|
|
||||||
throw new ImmediateResponseException($response, 1731071856);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$product = $date->getProduct();
|
// return 404 in case no travel info is available as last resort
|
||||||
|
if (null === $travelInfo) {
|
||||||
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) {
|
|
||||||
$response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
$response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
|
||||||
$GLOBALS['TYPO3_REQUEST'],
|
$GLOBALS['TYPO3_REQUEST'],
|
||||||
'Reiseinformationen nicht gefunden'
|
'Reiseinformationen nicht gefunden'
|
||||||
@@ -67,22 +49,39 @@ class TravelinfoController extends ActionController
|
|||||||
throw new ImmediateResponseException($response, 1731071856);
|
throw new ImmediateResponseException($response, 1731071856);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->view->assign('travelinfo', $travelinfo);
|
$this->handleRequest($travelCode, $travelInfo);
|
||||||
$this->view->assign('date', $date);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)) {
|
$this->handleRequest($travelCode, $travelInfo);
|
||||||
$travelDate = (\DateTimeImmutable::createFromFormat('dmY', $travelDate))->format('Y-m-d');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Date $date */
|
private function handleRequest(string $travelCode, Travelinfo $travelInfo)
|
||||||
$date = $this->dateRepository->findForProductAndDate($product, $travelDate)->getFirst();
|
{
|
||||||
|
|
||||||
$this->view->assign('travelinfo', $travelinfo);
|
// fetch travel data via API call
|
||||||
$this->view->assign('date', $date);
|
try {
|
||||||
|
$travelData = $this->apiClient->getTravel($travelCode);
|
||||||
|
} catch (ApiException $e) {
|
||||||
|
$travelData = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract courses from additional services
|
||||||
|
$courses = array_filter($travelData['additionalServices'] ?? [], function ($service) {
|
||||||
|
return 'KUR' === $service['subType'];
|
||||||
|
});
|
||||||
|
|
||||||
|
// extract board services from additional services
|
||||||
|
$board = array_filter($travelData['additionalServices'] ?? [], function ($service) {
|
||||||
|
return 'VPF' === $service['subType'];
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->view->assign('travelInfo', $travelInfo);
|
||||||
|
$this->view->assign('travelData', $travelData);
|
||||||
|
$this->view->assign('courses', $courses);
|
||||||
|
$this->view->assign('board', $board);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace EP\EpProducts\MyEP;
|
||||||
|
|
||||||
|
use League\OAuth2\Client\Provider\AbstractProvider;
|
||||||
|
use League\OAuth2\Client\Provider\GenericProvider;
|
||||||
|
use League\OAuth2\Client\Token\AccessToken;
|
||||||
|
use Symfony\Component\HttpClient\HttpClient;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||||
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
|
|
||||||
|
class ApiClient
|
||||||
|
{
|
||||||
|
private static ?AccessToken $accessToken = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ApiException
|
||||||
|
*/
|
||||||
|
public function getLastUpdateAt(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
$httpClient = $this->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',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace EP\EpProducts\MyEP;
|
||||||
|
|
||||||
|
class ApiException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
+6
-25
@@ -27,7 +27,7 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'palettes' => [
|
'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'],
|
--linebreak--,teasers'],
|
||||||
'text' => ['showitem' => 'subline,headline,--linebreak--,intro_text,--linebreak--, bus_info_text,--linebreak--,
|
'text' => ['showitem' => 'subline,headline,--linebreak--,intro_text,--linebreak--, bus_info_text,--linebreak--,
|
||||||
departure_text,--linebreak--,footer_text,--linebreak--,self_arranged_text,--linebreak--,additional_info,
|
departure_text,--linebreak--,footer_text,--linebreak--,self_arranged_text,--linebreak--,additional_info,
|
||||||
@@ -191,32 +191,13 @@ return [
|
|||||||
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
|
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
'product' => [
|
'travel_code' => [
|
||||||
'exclude' => false,
|
'exclude' => false,
|
||||||
'label' => 'Produkt',
|
'label' => 'Reisecode',
|
||||||
'config' => [
|
'config' => [
|
||||||
'type' => 'group',
|
'type' => 'input',
|
||||||
'internal_type' => 'db',
|
'size' => 30,
|
||||||
'allowed' => 'tx_epproducts_domain_model_product',
|
'eval' => 'trim,required'
|
||||||
'minitems' => 1,
|
|
||||||
'maxitems' => 1,
|
|
||||||
'size' => 1,
|
|
||||||
'fieldControl' => [
|
|
||||||
'elementBrowser' => [
|
|
||||||
'disabled' => true,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'fieldWizard' => [
|
|
||||||
'recordsOverview' => [
|
|
||||||
'disabled' => true,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'suggestOptions' => [
|
|
||||||
'default' => [
|
|
||||||
'additionalSearchFields' => 'code,name,keywords',
|
|
||||||
'orderBy' => 'code',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'hide_address' => [
|
'hide_address' => [
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
# customsubcategory=100=General
|
# cat=General; type=int; label=Searchresult PID
|
||||||
# cat=epproducts/100/100; type=int; label=Searchresult PID
|
|
||||||
searchPageUid = 0
|
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 =
|
||||||
|
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ $boot = function () {
|
|||||||
'EP.ep_products',
|
'EP.ep_products',
|
||||||
'travelinfo',
|
'travelinfo',
|
||||||
[
|
[
|
||||||
'Travelinfo' => 'index,travelCode',
|
'Travelinfo' => 'travelInfo,travelCode',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<f:link.typolink parameter="{f:if(condition: settings.logoLink, then: settings.logoLink, else: settings.defaultHomeUid)}"
|
<f:link.typolink parameter="{f:if(condition: settings.logoLink, then: settings.logoLink, else: settings.defaultHomeUid)}"
|
||||||
class="block lg:pb-4"
|
class="block lg:pb-4"
|
||||||
title="zur Startseite">
|
title="zur Startseite">
|
||||||
<f:image src="Logo"
|
<f:image src="{settings.logoImage}"
|
||||||
class="block w-auto {ep:themeClasses(classes: '{ep: \'h-10 lg:h-12\', sbw: \'h-14 lg:h-24\', snz: \'h-14 lg:h-24\', suz: \'h-14 lg:h-24\', uch: \'h-12 lg:h-20\', ser: \'h-10 lg:h-12\'}', key: settings.themekey)}"
|
class="block w-auto {ep:themeClasses(classes: '{ep: \'h-10 lg:h-12\', sbw: \'h-14 lg:h-24\', snz: \'h-14 lg:h-24\', suz: \'h-14 lg:h-24\', uch: \'h-12 lg:h-20\', ser: \'h-10 lg:h-12\'}', key: settings.themekey)}"
|
||||||
alt="Logo"/>
|
alt="Bildmarke"/>
|
||||||
</f:link.typolink>
|
</f:link.typolink>
|
||||||
<f:link.typolink parameter="{settings.myEpPageUid}"
|
<f:link.typolink parameter="{settings.myEpPageUid}"
|
||||||
title="My E&P"
|
title="My E&P"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<f:section name="Main">
|
<f:section name="Main">
|
||||||
<div class="container relative">
|
<div class="container relative">
|
||||||
<div class="h-80 lg:h-128">
|
<div class="h-80 lg:h-128">
|
||||||
<f:image image="{travelinfo.headerImage}"
|
<f:image image="{travelInfo.headerImage}"
|
||||||
width="1280"
|
width="1280"
|
||||||
alt="Reiseinformationen"
|
alt="Reiseinformationen"
|
||||||
class="block w-full h-full object-cover object-center" />
|
class="block w-full h-full object-cover object-center" />
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 lg:grid-cols-4">
|
<div class="grid grid-cols-2 lg:grid-cols-4">
|
||||||
<f:variable name="bgColors" value="{0: 'bg-[#165883]/90', 1: 'bg-[#9DBACE]/90', 2: 'bg-[#3396E4]/90', 3: 'bg-[#0268AA]/90'}"/>
|
<f:variable name="bgColors" value="{0: 'bg-[#165883]/90', 1: 'bg-[#9DBACE]/90', 2: 'bg-[#3396E4]/90', 3: 'bg-[#0268AA]/90'}"/>
|
||||||
<f:variable name="linksCount" value="{travelinfo.links -> f:count()}"/>
|
<f:variable name="linksCount" value="{travelInfo.links -> f:count()}"/>
|
||||||
<f:for each="{travelinfo.links}" as="link" iteration="iteration">
|
<f:for each="{travelInfo.links}" as="link" iteration="iteration">
|
||||||
<f:render section="Link" arguments="{item: link, color: '{bgColors.{iteration.index}}'}"/>
|
<f:render section="Link" arguments="{item: link, color: '{bgColors.{iteration.index}}'}"/>
|
||||||
</f:for>
|
</f:for>
|
||||||
<a href="#goodtoknow"
|
<a href="#goodtoknow"
|
||||||
@@ -40,13 +40,13 @@
|
|||||||
<div class="pb-32 pt-32 lg:pt-52 -mt-24 bg-ep-primary-bg">
|
<div class="pb-32 pt-32 lg:pt-52 -mt-24 bg-ep-primary-bg">
|
||||||
<div class="container px-8 lg:px-24" data-rte-content>
|
<div class="container px-8 lg:px-24" data-rte-content>
|
||||||
<h1 class="headline--underlined headline--has-subline">
|
<h1 class="headline--underlined headline--has-subline">
|
||||||
<span class="subline">{travelinfo.subline}</span>
|
<span class="subline">{travelInfo.subline}</span>
|
||||||
{travelinfo.headline}
|
{travelInfo.headline}
|
||||||
</h1>
|
</h1>
|
||||||
{travelinfo.introText -> f:format.html()}
|
{travelInfo.introText -> f:format.html()}
|
||||||
<f:if condition="{date}">
|
<f:if condition="{date}">
|
||||||
<p>
|
<p>
|
||||||
{date.product.name} {date.dateStart -> f:format.date(format: 'd.m.Y')} - {date.dateEnd -> f:format.date(format: 'd.m.Y')}
|
{travelData.label} {travelData.dateFrom -> f:format.date(format: 'd.m.Y')} - {travelData.dateTo -> f:format.date(format: 'd.m.Y')}
|
||||||
</p>
|
</p>
|
||||||
</f:if>
|
</f:if>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,20 +63,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
|
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
|
||||||
<f:if condition="{date}">
|
<f:if condition="{date}">
|
||||||
<f:if condition="{travelinfo.hideAddress}">
|
<f:if condition="{travelInfo.hideAddress}">
|
||||||
<f:else>
|
<f:else>
|
||||||
<h3 class="text-white">
|
<h3 class="text-white">
|
||||||
Adresse
|
Adresse
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
{date.hotel.name}
|
{travelData.hotel.name}
|
||||||
<br>
|
<br>
|
||||||
{date.hotel.address -> f:format.nl2br()}
|
{travelData.hotel.street}
|
||||||
|
<br>
|
||||||
|
{travelData.hotel.country} {travelData.hotel.city}
|
||||||
</p>
|
</p>
|
||||||
</f:else>
|
</f:else>
|
||||||
</f:if>
|
</f:if>
|
||||||
<f:if condition="{date.pickups -> f:count()} > 0">
|
<f:if condition="{travelData.pickupsTo -> f:count()} > 0">
|
||||||
<f:if condition="{date.pickups -> f:count()} <= 3">
|
<f:if condition="{travelData.pickupsTo -> f:count()} <= 3">
|
||||||
<f:then>
|
<f:then>
|
||||||
<f:render section="Pickups" arguments="{_all}"/>
|
<f:render section="Pickups" arguments="{_all}"/>
|
||||||
</f:then>
|
</f:then>
|
||||||
@@ -98,11 +100,11 @@
|
|||||||
</f:if>
|
</f:if>
|
||||||
</f:if>
|
</f:if>
|
||||||
</f:if>
|
</f:if>
|
||||||
<f:if condition="{travelinfo.selfArrangedText}">
|
<f:if condition="{travelInfo.selfArrangedText}">
|
||||||
<h3 class="text-white">
|
<h3 class="text-white">
|
||||||
Eigenanreise
|
Eigenanreise
|
||||||
</h3>
|
</h3>
|
||||||
{travelinfo.selfArrangedText -> f:format.html()}
|
{travelInfo.selfArrangedText -> f:format.html()}
|
||||||
</f:if>
|
</f:if>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,13 +116,13 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="container px-4 lg:px-24 flex flex-col space-y-16 pb-16">
|
<div class="container px-4 lg:px-24 flex flex-col space-y-16 pb-16">
|
||||||
<f:for each="{travelinfo.teasers}" as="teaser">
|
<f:for each="{travelInfo.teasers}" as="teaser">
|
||||||
<f:render section="Teaser" arguments="{_all}"/>
|
<f:render section="Teaser" arguments="{_all}"/>
|
||||||
</f:for>
|
</f:for>
|
||||||
</div>
|
</div>
|
||||||
<div id="goodtoknow" class="bg-ep-primary-bg">
|
<div id="goodtoknow" class="bg-ep-primary-bg">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<f:if condition="{travelinfo.additionalInfo}">
|
<f:if condition="{travelInfo.additionalInfo}">
|
||||||
<div class="flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white bg-[#B48F1D] px-8 lg:px-0 py-8">
|
<div class="flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white bg-[#B48F1D] px-8 lg:px-0 py-8">
|
||||||
<div class="lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8">
|
<div class="lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8">
|
||||||
<div class="w-32 h-32 xl:w-16 xl:h-16 text-[#F9C700]">
|
<div class="w-32 h-32 xl:w-16 xl:h-16 text-[#F9C700]">
|
||||||
@@ -131,11 +133,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
|
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
|
||||||
{travelinfo.additionalInfo -> f:format.html()}
|
{travelInfo.additionalInfo -> f:format.html()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</f:if>
|
</f:if>
|
||||||
<f:if condition="{travelinfo.importantPhoneNumbers}">
|
<f:if condition="{travelInfo.importantPhoneNumbers}">
|
||||||
<div class="flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white bg-[#F8C62F] px-8 lg:px-0 py-8">
|
<div class="flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white bg-[#F8C62F] px-8 lg:px-0 py-8">
|
||||||
<div class="lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8">
|
<div class="lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8">
|
||||||
<div class="text-2xl lg:text-4xl text-white uppercase font-bold">
|
<div class="text-2xl lg:text-4xl text-white uppercase font-bold">
|
||||||
@@ -143,18 +145,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
|
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
|
||||||
<f:if condition="{date.guide}">
|
<f:if condition="{travelData.guide}">
|
||||||
<p>
|
<p>
|
||||||
<strong>Busbegleitung / Reiseleitung</strong>
|
<strong>Busbegleitung / Reiseleitung</strong>
|
||||||
<br>
|
<br>
|
||||||
{date.guide.name} {date.guide.phone}
|
{travelData.guide.name} {travelData.guide.phone}
|
||||||
</p>
|
</p>
|
||||||
</f:if>
|
</f:if>
|
||||||
{travelinfo.importantPhoneNumbers -> f:format.html()}
|
{travelInfo.importantPhoneNumbers -> f:format.html()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</f:if>
|
</f:if>
|
||||||
<f:if condition="{travelinfo.importantLinks}">
|
<f:if condition="{travelInfo.importantLinks}">
|
||||||
<div class="flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white bg-[#18527B] px-8 lg:px-0 py-8">
|
<div class="flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white bg-[#18527B] px-8 lg:px-0 py-8">
|
||||||
<div class="lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8">
|
<div class="lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8">
|
||||||
<div class="text-2xl lg:text-4xl text-white uppercase font-bold">
|
<div class="text-2xl lg:text-4xl text-white uppercase font-bold">
|
||||||
@@ -162,18 +164,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
|
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
|
||||||
{travelinfo.importantLinks -> f:format.html()}
|
{travelInfo.importantLinks -> f:format.html()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</f:if>
|
</f:if>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container px-4 lg:px-24 py-16">
|
<div class="container px-4 lg:px-24 py-16">
|
||||||
{travelinfo.footerText -> f:format.html()}
|
{travelInfo.footerText -> f:format.html()}
|
||||||
<f:image src="EXT:ep_theme/Resources/Public/images/signature_ep_team.png" class="block w-full max-w-64 h-auto mt-8" alt="" />
|
<f:image src="EXT:ep_theme/Resources/Public/images/signature_ep_team.png" class="block w-full max-w-64 h-auto mt-8" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<f:image image="{travelinfo.footerImage}" width="1028" class="block w-full h-auto" alt="" />
|
<f:image image="{travelInfo.footerImage}" width="1028" class="block w-full h-auto" alt="" />
|
||||||
</div>
|
</div>
|
||||||
</f:section>
|
</f:section>
|
||||||
|
|
||||||
@@ -196,20 +198,28 @@
|
|||||||
<h2 class="headline--primary">
|
<h2 class="headline--primary">
|
||||||
{teaser.title}
|
{teaser.title}
|
||||||
</h2>
|
</h2>
|
||||||
<f:if condition="{date}">
|
<f:if condition="{travelData}">
|
||||||
<f:if condition="{teaser.category} == 'food'">
|
<f:if condition="{teaser.category} == 'food'">
|
||||||
<p>
|
<f:if condition="{board -> f:count()} > 0">
|
||||||
{date.board}
|
<f:then>
|
||||||
</p>
|
<ul>
|
||||||
|
<f:for as="item" each="{board}">
|
||||||
|
<li>
|
||||||
|
{item.label}
|
||||||
|
</li>
|
||||||
|
</f:for>
|
||||||
|
</ul>
|
||||||
|
</f:then>
|
||||||
|
</f:if>
|
||||||
</f:if>
|
</f:if>
|
||||||
<f:if condition="{teaser.category} == 'course'">
|
<f:if condition="{teaser.category} == 'course'">
|
||||||
<f:if condition="{date.activeCoursesForTravelinfo -> f:count()} > 0">
|
<f:if condition="{courses -> f:count()} > 0">
|
||||||
<f:then>
|
<f:then>
|
||||||
<p>
|
<p>
|
||||||
<strong>Folgende Kurse finden statt</strong>
|
<strong>Folgende Kurse finden statt</strong>
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<f:for as="course" each="{date.activeCoursesForTravelinfo}">
|
<f:for as="course" each="{courses}">
|
||||||
<li>
|
<li>
|
||||||
{course.label}
|
{course.label}
|
||||||
</li>
|
</li>
|
||||||
@@ -257,7 +267,7 @@
|
|||||||
<p>
|
<p>
|
||||||
Bitte findet euch mindestens <strong>20 Minuten vor Abfahrt</strong> an der gebuchten Haltestelle ein.
|
Bitte findet euch mindestens <strong>20 Minuten vor Abfahrt</strong> an der gebuchten Haltestelle ein.
|
||||||
</p>
|
</p>
|
||||||
{travelinfo.busInfoText -> f:format.html()}
|
{travelInfo.busInfoText -> f:format.html()}
|
||||||
<table class="w-full bg-zinc-200 text-zinc-800 text-xs md:text-base mb-4">
|
<table class="w-full bg-zinc-200 text-zinc-800 text-xs md:text-base mb-4">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="border-r border-white/50 px-2 py-1 md:p-2 text-left text-sm md:text-base">
|
<th class="border-r border-white/50 px-2 py-1 md:p-2 text-left text-sm md:text-base">
|
||||||
@@ -267,18 +277,18 @@
|
|||||||
Datum/Uhrzeit
|
Datum/Uhrzeit
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<f:for each="{date.pickups}" as="pickup">
|
<f:for each="{travelData.pickupsTo}" as="pickup">
|
||||||
<tr class="odd:bg-zinc-50 align-top">
|
<tr class="odd:bg-zinc-50 align-top">
|
||||||
<td class="border-r border-zinc-200 px-2 py-1 md:p-2">
|
<td class="border-r border-zinc-200 px-2 py-1 md:p-2">
|
||||||
{pickup.city}<br><span class="text-xs">{pickup.street}</span>
|
{pickup.city}<br><span class="text-xs">{pickup.street}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="border-r border-zinc-200 px-2 py-1 md:p-2">
|
<td class="border-r border-zinc-200 px-2 py-1 md:p-2">
|
||||||
{pickup.date}<br>{f:if(condition: pickup.time, then: '{pickup.time} Uhr', else: '???')}
|
{pickup.time -> f:format.date(format: 'd.m.Y')}<br>{pickup.time -> f:format.date(format: 'H:i')} Uhr
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</f:for>
|
</f:for>
|
||||||
</table>
|
</table>
|
||||||
{travelinfo.departureText -> f:format.html()}
|
{travelInfo.departureText -> f:format.html()}
|
||||||
</f:section>
|
</f:section>
|
||||||
|
|
||||||
<f:section name="Icon">
|
<f:section name="Icon">
|
||||||
|
|||||||
Reference in New Issue
Block a user