From a46918de9da68f72baaf813246e93d99c5ca6198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 21 Oct 2026 14:01:20 +0200 Subject: [PATCH] chore: copy ep_products and ep_theme from EP/website@9593cc8 Copied unchanged from public/typo3conf/ext/ of EP/website at 9593cc8631354f8a363d6609de8b7433f669a9a0 (Git-tracked files only). History stays in the old repository. Only composer.json files are added so the extensions install as path packages. Co-Authored-By: Claude Opus 5.5 --- composer.json | 2 + composer.lock | 427 +++- .../Classes/BpnConnect/ApiClient.php | 140 ++ .../Classes/BpnConnect/ApiException.php | 7 + .../Classes/Command/ContingentCommand.php | 75 + .../Classes/Command/DateCommandController.php | 120 + .../Command/ExportCommandController.php | 178 ++ .../Classes/Command/GroupsSwissCommand.php | 85 + .../Command/SnowreportCommandController.php | 62 + .../Classes/ContentFeed/ContentFeedApi.php | 165 ++ .../ContentFeed/ContentFeedConfiguration.php | 75 + .../Classes/ContentFeed/ManifestBuilder.php | 379 ++++ .../Classes/ContentFeed/PageTree.php | 131 ++ .../Classes/ContentFeed/RecordFinder.php | 152 ++ .../Classes/ContentFeed/RecordSerializer.php | 513 +++++ .../Classes/ContentFeed/RteLinkResolver.php | 104 + .../Classes/ContentFeed/UrlResolver.php | 214 ++ .../Controller/AjaxCalendarController.php | 391 ++++ .../Controller/AjaxContingentController.php | 102 + .../Classes/Controller/AjaxDateController.php | 148 ++ .../Controller/AjaxFilterPanelController.php | 100 + .../Controller/AjaxSearchController.php | 132 ++ .../Controller/AjaxTableController.php | 386 ++++ .../Controller/AjaxTeaserPopupController.php | 17 + .../Controller/AjaxWatchlistController.php | 104 + .../Classes/Controller/BackendController.php | 149 ++ .../Classes/Controller/CityController.php | 97 + .../Classes/Controller/ConceptController.php | 104 + .../Classes/Controller/CountryController.php | 79 + .../Classes/Controller/FaqController.php | 56 + .../Classes/Controller/HotelController.php | 152 ++ .../Classes/Controller/IcalController.php | 56 + .../Classes/Controller/MapController.php | 123 ++ .../Controller/NewsletterController.php | 80 + .../Controller/PricetableController.php | 41 + .../Classes/Controller/ProductController.php | 261 +++ .../Classes/Controller/RegionController.php | 95 + .../Classes/Controller/ResellerController.php | 99 + .../Classes/Controller/SearchController.php | 168 ++ .../Controller/SearchbarController.php | 87 + .../Controller/SnowreportController.php | 59 + .../Classes/Controller/TeamController.php | 96 + .../Controller/TravelinfoController.php | 105 + .../Controller/WatchlistController.php | 42 + .../FacebookCatalogCsvFormatter.php | 66 + .../GoogleBusinessFeedCsvFormatter.php | 58 + .../Domain/Model/AbstractDestination.php | 420 ++++ .../Classes/Domain/Model/Badge.php | 97 + .../ep_products/Classes/Domain/Model/City.php | 199 ++ .../Classes/Domain/Model/Concept.php | 434 ++++ .../Classes/Domain/Model/Contingent.php | 353 +++ .../Classes/Domain/Model/Country.php | 158 ++ .../ep_products/Classes/Domain/Model/Date.php | 639 ++++++ .../Domain/Model/Dto/FilterOptions.php | 347 +++ .../Domain/Model/Dto/GroupsPriceInquiry.php | 484 ++++ .../Dto/NewsletterSubscriptionRequest.php | 113 + .../ep_products/Classes/Domain/Model/Fact.php | 96 + .../ep_products/Classes/Domain/Model/Faq.php | 75 + .../Classes/Domain/Model/GroupsPriceBoard.php | 111 + .../Domain/Model/GroupsPriceConfig.php | 201 ++ .../Domain/Model/GroupsPriceOption.php | 160 ++ .../Classes/Domain/Model/Hotel.php | 1273 +++++++++++ .../Classes/Domain/Model/Infobox.php | 73 + .../Classes/Domain/Model/Journey.php | 117 + .../Classes/Domain/Model/MatchCode.php | 54 + .../Classes/Domain/Model/Officetip.php | 96 + .../Classes/Domain/Model/Product.php | 1209 ++++++++++ .../Classes/Domain/Model/Region.php | 476 ++++ .../Classes/Domain/Model/Reseller.php | 127 ++ .../ep_products/Classes/Domain/Model/Room.php | 48 + .../Classes/Domain/Model/SearchResult.php | 85 + .../Classes/Domain/Model/Snowreport.php | 392 ++++ .../Classes/Domain/Model/Teammember.php | 187 ++ .../Classes/Domain/Model/TeaserInterface.php | 50 + .../Classes/Domain/Model/Travelinfo.php | 329 +++ .../Classes/Domain/Model/TravelinfoLink.php | 61 + .../Classes/Domain/Model/TravelinfoTeaser.php | 76 + .../Classes/Domain/Model/Webcam.php | 138 ++ .../Domain/Repository/AbstractRepository.php | 230 ++ .../Domain/Repository/CityRepository.php | 59 + .../Domain/Repository/ConceptRepository.php | 31 + .../Repository/ContingentRepository.php | 172 ++ .../Domain/Repository/CountryRepository.php | 31 + .../Domain/Repository/DateRepository.php | 45 + .../Domain/Repository/FaqRepository.php | 62 + .../GroupsPriceOptionRepository.php | 56 + .../Domain/Repository/HotelRepository.php | 272 +++ .../Domain/Repository/InfoboxRepository.php | 21 + .../Domain/Repository/MatchCodeRepository.php | 31 + .../Domain/Repository/ProductRepository.php | 414 ++++ .../Domain/Repository/RegionRepository.php | 56 + .../Domain/Repository/ResellerRepository.php | 31 + .../Domain/Repository/RoomRepository.php | 31 + .../Repository/SnowreportRepository.php | 71 + .../Repository/TeammemberRepository.php | 52 + .../Repository/TravelinfoRepository.php | 6 + .../Domain/Repository/WebcamRepository.php | 31 + .../Classes/Domain/Serialization/City.php | 57 + .../Classes/Domain/Serialization/Date.php | 63 + .../Classes/Domain/Serialization/Hotel.php | 56 + .../Classes/Domain/Serialization/Product.php | 104 + .../Classes/Domain/Serialization/Region.php | 61 + .../Classes/Domain/Serialization/Room.php | 53 + .../Classes/Domain/Serialization/Services.php | 73 + .../ep_products/Classes/EpTeam/ApiClient.php | 244 +++ .../Classes/EpTeam/ApiException.php | 7 + .../SystemInformationToolbarListener.php | 27 + .../Classes/Hooks/PageLayoutView.php | 258 +++ .../ep_products/Classes/Hooks/Tcemain.php | 119 + .../Classes/Http/ApiKeyAuthenticator.php | 45 + packages/ep_products/Classes/Log/Writer.php | 40 + .../Classes/Middleware/ApiMiddleware.php | 328 +++ .../ContentPageTypeGuardMiddleware.php | 65 + .../StagingEnvironmentMiddleware.php | 43 + .../StaticSearchParamsMiddleware.php | 71 + .../ep_products/Classes/MyEP/ApiClient.php | 506 +++++ .../ep_products/Classes/MyEP/ApiException.php | 7 + .../Routing/Aspect/PassthroughMapper.php | 50 + .../Classes/Service/AbstractImageService.php | 111 + .../Classes/Service/ApiImportSource.php | 233 ++ .../Classes/Service/ContingentDataService.php | 113 + .../Service/ContingentImportService.php | 341 +++ .../Classes/Service/DateImportService.php | 970 ++++++++ .../Classes/Service/DateService.php | 529 +++++ .../Classes/Service/EmailService.php | 90 + .../Classes/Service/FilterService.php | 342 +++ .../Classes/Service/FilterSettingsEncoder.php | 112 + .../Classes/Service/GroupsSwissService.php | 267 +++ .../Classes/Service/HotelDataService.php | 274 +++ .../Classes/Service/HotelImageService.php | 58 + .../Classes/Service/IcalService.php | 159 ++ .../Service/ImportTimestampService.php | 61 + .../Classes/Service/LabelService.php | 106 + .../Classes/Service/LocalImportSource.php | 56 + .../Classes/Service/LogService.php | 204 ++ .../Classes/Service/MyEpImageService.php | 63 + .../Service/MyEpUrlResolverService.php | 55 + .../Classes/Service/PickupOverviewService.php | 82 + .../Classes/Service/ProductDataService.php | 233 ++ .../Classes/Service/ProductImageService.php | 66 + .../Classes/Service/ProductImport.php | 56 + .../Classes/Service/ProductImportSource.php | 31 + .../Service/ProductImportSourceFactory.php | 41 + .../Classes/Service/ResellerDataService.php | 187 ++ .../Classes/Service/RoomTypeResolver.php | 129 ++ .../Classes/Service/SearchResultService.php | 287 +++ .../Classes/Service/SftpImportException.php | 7 + .../Classes/Service/SftpImportSource.php | 540 +++++ .../Service/SnowreportImportService.php | 621 ++++++ .../Classes/Service/XmlProductReader.php | 369 ++++ .../Classes/Task/ImportContingentsTask.php | 49 + .../Classes/Task/ImportDatesTask.php | 74 + .../Classes/Task/ImportSnowreportTask.php | 46 + .../Classes/Traits/DbConnectionTrait.php | 49 + .../RequestArgumentTypeConversionTrait.php | 53 + .../Classes/Updates/TeamSlugUpdater.php | 121 + .../Classes/UserFunc/TcaProcFunc.php | 48 + .../Classes/Utility/BookingUrlUtility.php | 40 + .../Classes/Utility/DateUtility.php | 82 + .../Classes/Utility/HotelIconUtility.php | 83 + .../Classes/Utility/SerializationUtility.php | 108 + .../Classes/Utility/SettingsUtility.php | 65 + .../Classes/Utility/TypeConversionUtility.php | 32 + .../ep_products/Configuration/Commands.php | 15 + .../FlexForms/flexform_city_detail.xml | 40 + .../FlexForms/flexform_city_teaser.xml | 79 + .../FlexForms/flexform_concept_detail.xml | 69 + .../FlexForms/flexform_country.xml | 46 + .../Configuration/FlexForms/flexform_faq.xml | 32 + .../FlexForms/flexform_hotel_detail.xml | 46 + .../FlexForms/flexform_hotel_detail_event.xml | 51 + .../FlexForms/flexform_hotel_teaser.xml | 217 ++ ...flexform_hotel_teaser_date_independent.xml | 176 ++ .../Configuration/FlexForms/flexform_maps.xml | 47 + .../FlexForms/flexform_pricetable.xml | 65 + .../FlexForms/flexform_product_detail.xml | 75 + .../FlexForms/flexform_product_pricetable.xml | 51 + .../FlexForms/flexform_product_teaser.xml | 252 +++ .../FlexForms/flexform_region_detail.xml | 54 + .../FlexForms/flexform_region_teaser.xml | 53 + .../FlexForms/flexform_snowreport.xml | 32 + .../FlexForms/flexform_teammembers.xml | 53 + .../Configuration/RequestMiddlewares.php | 39 + .../ep_products/Configuration/Services.yaml | 61 + .../TCA/Overrides/sys_domain.php | 36 + .../TCA/Overrides/sys_template.php | 9 + .../TCA/Overrides/tt_content.php | 271 +++ .../tx_epproducts_domain_model_teammember.php | 12 + .../TCA/tx_epproducts_domain_model_badge.php | 144 ++ .../TCA/tx_epproducts_domain_model_city.php | 470 ++++ .../tx_epproducts_domain_model_concept.php | 415 ++++ .../tx_epproducts_domain_model_contingent.php | 236 ++ .../tx_epproducts_domain_model_country.php | 444 ++++ .../TCA/tx_epproducts_domain_model_date.php | 446 ++++ .../TCA/tx_epproducts_domain_model_fact.php | 145 ++ .../TCA/tx_epproducts_domain_model_faq.php | 170 ++ ...products_domain_model_groupspriceboard.php | 151 ++ ...roducts_domain_model_groupspriceconfig.php | 186 ++ ...roducts_domain_model_groupspriceoption.php | 175 ++ .../TCA/tx_epproducts_domain_model_hotel.php | 850 +++++++ ..._epproducts_domain_model_hotel_mapping.php | 141 ++ .../tx_epproducts_domain_model_infobox.php | 145 ++ .../tx_epproducts_domain_model_journey.php | 170 ++ .../tx_epproducts_domain_model_matchcode.php | 139 ++ .../tx_epproducts_domain_model_officetip.php | 147 ++ .../tx_epproducts_domain_model_product.php | 951 ++++++++ .../TCA/tx_epproducts_domain_model_region.php | 609 +++++ .../tx_epproducts_domain_model_reseller.php | 154 ++ .../tx_epproducts_domain_model_snowreport.php | 286 +++ ...oducts_domain_model_staticsearchparams.php | 258 +++ .../tx_epproducts_domain_model_teammember.php | 214 ++ .../tx_epproducts_domain_model_travelinfo.php | 374 ++++ ...epproducts_domain_model_travelinfolink.php | 167 ++ ...products_domain_model_travelinfoteaser.php | 220 ++ .../TCA/tx_epproducts_domain_model_webcam.php | 164 ++ .../TypoScript/constants.typoscript | 68 + .../Configuration/TypoScript/setup.typoscript | 303 +++ packages/ep_products/README.md | 17 + .../ep_products/Resources/Private/.htaccess | 11 + .../Resources/Private/Language/locallang.xlf | 68 + .../Private/Language/locallang_mod.xlf | 17 + .../Resources/Private/Layouts/Backend.html | 1 + .../Private/Templates/Backend/Index.html | 47 + .../Resources/Public/Icons/logo.svg | 20 + .../Resources/Public/Icons/relation.gif | Bin 0 -> 233 bytes .../tx_epproducts_domain_model_badge.gif | Bin 0 -> 172 bytes .../Icons/tx_epproducts_domain_model_city.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_concept.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_contingent.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_country.gif | Bin 0 -> 230 bytes .../Icons/tx_epproducts_domain_model_date.gif | Bin 0 -> 230 bytes .../Icons/tx_epproducts_domain_model_fact.gif | Bin 0 -> 230 bytes .../Icons/tx_epproducts_domain_model_faq.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_hotel.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_journey.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_matchcode.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_officetip.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_product.gif | Bin 0 -> 533 bytes .../tx_epproducts_domain_model_region.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_reseller.gif | Bin 0 -> 230 bytes .../Icons/tx_epproducts_domain_model_room.gif | Bin 0 -> 230 bytes ...tx_epproducts_domain_model_roommapping.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_snowreport.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_teammember.gif | Bin 0 -> 230 bytes .../tx_epproducts_domain_model_webcam.gif | Bin 0 -> 230 bytes packages/ep_products/composer.json | 32 + packages/ep_products/ext_conf_template.txt | 95 + packages/ep_products/ext_emconf.php | 20 + packages/ep_products/ext_icon.gif | Bin 0 -> 1425 bytes packages/ep_products/ext_localconf.php | 358 +++ packages/ep_products/ext_tables.php | 22 + packages/ep_products/ext_tables.sql | 1698 ++++++++++++++ packages/ep_theme/Classes/.htaccess | 1 + .../Classes/Controller/BackendController.php | 83 + .../DataProcessing/ContextProcessor.php | 130 ++ .../DataProcessing/FlexFormProcessor.php | 60 + .../DataProcessing/ProductRatingProcessor.php | 86 + .../DataProcessing/SliderProcessor.php | 116 + .../DataProcessing/TeaserImageProcessor.php | 59 + .../DataProcessing/TeaserProcessor.php | 130 ++ .../Classes/Domain/Model/Dto/ContactForm.php | 351 +++ .../ep_theme/Classes/Domain/Model/Page.php | 102 + .../Domain/Repository/PageRepository.php | 35 + .../ConfigurationMissingException.php | 6 + .../TemplateFileMissingException.php | 6 + .../Classes/Form/AbstractMappedFinisher.php | 156 ++ .../Classes/Form/AddReferrerFinisher.php | 74 + .../Classes/Form/ApplicationApiFinisher.php | 83 + .../ep_theme/Classes/Form/BpnApiFinisher.php | 35 + .../ep_theme/Classes/Form/WebhookFinisher.php | 40 + .../AbstractPreviewRenderer.php | 146 ++ .../DisrupterPreviewRenderer.php | 20 + .../DisturberPreviewRenderer.php | 8 + .../PageLayoutView/GmapPreviewRenderer.php | 26 + .../PageLayoutView/LineupPreviewRenderer.php | 31 + .../TeaserBookingLinkPreviewRenderer.php | 21 + .../TeaserCityPreviewRenderer.php | 20 + .../TeaserConceptPreviewRenderer.php | 20 + .../TeaserCountryPreviewRenderer.php | 20 + .../TeaserHotelPreviewRenderer.php | 20 + .../TeaserProductPreviewRenderer.php | 24 + .../TeaserRegionPreviewRenderer.php | 20 + packages/ep_theme/Classes/Hooks/Tcemain.php | 62 + .../MetaTag/OpenGraphMetaTagManager.php | 69 + .../Classes/Middleware/ReferrerMiddleware.php | 43 + .../ep_theme/Classes/Service/EmailService.php | 102 + .../ViewHelpers/ArgumentPrefixViewHelper.php | 70 + .../Calendar/ContingentViewHelper.php | 201 ++ .../ViewHelpers/ComfortCategoryViewHelper.php | 92 + .../ViewHelpers/ContainerViewHelper.php | 117 + .../ViewHelpers/ContextClassesViewHelper.php | 71 + .../ViewHelpers/DateRangeViewHelper.php | 67 + .../FilterSettingsEncoderViewHelper.php | 75 + .../ViewHelpers/GmapsUrlViewHelper.php | 83 + .../ViewHelpers/HotelCategoriesViewHelper.php | 72 + .../ViewHelpers/HotelCategoryViewHelper.php | 73 + .../Classes/ViewHelpers/IconViewHelper.php | 41 + .../Classes/ViewHelpers/MyEpUrlViewHelper.php | 28 + .../ViewHelpers/NightsOptionsViewHelper.php | 81 + .../ViewHelpers/PriceRangeViewHelper.php | 66 + .../Classes/ViewHelpers/PriceViewHelper.php | 45 + .../Classes/ViewHelpers/RatingViewHelper.php | 86 + .../Stimulus/AbstractStimulusViewHelper.php | 41 + .../ViewHelpers/Stimulus/ActionViewHelper.php | 88 + .../Stimulus/ControllerViewHelper.php | 81 + .../ViewHelpers/Stimulus/TargetViewHelper.php | 68 + .../ViewHelpers/ThemeClassesViewHelper.php | 66 + .../ViewHelpers/Uri/AjaxViewHelper.php | 89 + .../ViewHelpers/Uri/BookingViewHelper.php | 92 + .../Uri/ExternalImageViewHelper.php | 71 + .../ViewHelpers/Uri/ProductViewHelper.php | 103 + .../ViewHelpers/Uri/VideoViewHelper.php | 75 + packages/ep_theme/Configuration/.htaccess | 1 + .../Extbase/Persistence/Classes.php | 9 + .../FlexForms/flexform_disturber.xml | 64 + .../ep_theme/Configuration/RTE/Default.yaml | 41 + .../ep_theme/Configuration/RTE/Minimal.yaml | 37 + .../Configuration/RequestMiddlewares.php | 12 + packages/ep_theme/Configuration/Services.yaml | 9 + .../Configuration/TCA/Overrides/pages.php | 155 ++ .../Configuration/TCA/Overrides/sys_file.php | 10 + .../TCA/Overrides/sys_template.php | 13 + .../TCA/Overrides/tt_content.php | 193 ++ .../Overrides/tt_content_element_button.php | 44 + .../Overrides/tt_content_element_calendar.php | 30 + .../Overrides/tt_content_element_contact.php | 44 + .../tt_content_element_container.php | 52 + .../tt_content_element_deal_of_the_week.php | 39 + .../tt_content_element_disrupter.php | 54 + .../tt_content_element_disturber.php | 59 + .../tt_content_element_event_pricetable.php | 30 + .../tt_content_element_faq_overview.php | 28 + .../TCA/Overrides/tt_content_element_gmap.php | 30 + .../Overrides/tt_content_element_lineup.php | 50 + .../Overrides/tt_content_element_nlform.php | 33 + .../Overrides/tt_content_element_teaser.php | 256 +++ .../Overrides/tt_content_element_ytvideo.php | 38 + .../tx_eptheme_domain_model_addresscheck.php | 169 ++ .../TCA/tx_eptheme_domain_model_disrupter.php | 211 ++ .../TCA/tx_eptheme_domain_model_lineup.php | 224 ++ .../TCA/tx_eptheme_domain_model_slide.php | 255 +++ .../Page/Extensions/GridElements.tsconfig | 180 ++ .../Configuration/TSconfig/Page/Main.tsconfig | 7 + .../TSconfig/Page/Mod/SHARED.tsconfig | 6 + .../Mod/WebLayout/BackendLayouts.tsconfig | 1 + .../WebLayout/BackendLayouts/Detail.tsconfig | 29 + .../BackendLayouts/Frontpage.tsconfig | 21 + .../BackendLayouts/FrontpageEvent.tsconfig | 22 + .../WebLayout/BackendLayouts/Popup.tsconfig | 21 + .../WebLayout/BackendLayouts/Search.tsconfig | 29 + .../WebLayout/BackendLayouts/Subpage.tsconfig | 29 + .../SubpageSidebarLeft.tsconfig | 47 + .../SubpageSidebarLeftEvent.tsconfig | 47 + .../SubpageSidebarRight.tsconfig | 47 + .../SubpageSidebarRightEvent.tsconfig | 47 + .../BackendLayouts/Travelinfo.tsconfig | 21 + .../Mod/Wizards/NewContentElement.tsconfig | 151 ++ .../Configuration/TSconfig/Page/RTE.tsconfig | 21 + .../TSconfig/Page/TCEFORM.tsconfig | 179 ++ .../TSconfig/Page/TCEMAIN.tsconfig | 6 + .../ContentElements/button.typoscript | 8 + .../ContentElements/calendar.typoscript | 14 + .../ContentElements/contact.typoscript | 23 + .../ContentElements/container.typoscript | 43 + .../ContentElements/disrupter.typoscript | 28 + .../ContentElements/disturber.typoscript | 13 + .../event_pricetable.typoscript | 10 + .../ContentElements/faq_overview.typoscript | 17 + .../ContentElements/gmap.typoscript | 23 + .../ContentElements/lineup.typoscript | 24 + .../ContentElements/list.typoscript | 4 + .../ContentElements/nlform.typoscript | 9 + .../ContentElements/teaser.typoscript | 31 + .../ContentElements/ytvideo.typoscript | 4 + .../Extensions/EpProducts.typoscript | 24 + .../Extensions/FluidStyledContent.typoscript | 14 + .../TypoScript/Extensions/Form.typoscript | 30 + .../Extensions/GridElements.typoscript | 20 + .../TypoScript/Extensions/News.typoscript | 13 + .../TypoScript/Extensions/Replacer.typoscript | 26 + .../TypoScript/Extensions/Seo.typoscript | 18 + .../Extensions/Typo3Encore.typoscript | 10 + .../TypoScript/Library/Breadcrumb.typoscript | 13 + .../Library/DynamicContent.typoscript | 49 + .../TypoScript/Library/ParseFunc.typoscript | 124 ++ .../TypoScript/Library/SearchBar.typoscript | 21 + .../TypoScript/Library/Sitemap.typoscript | 135 ++ .../TypoScript/Page/Ajax.typoscript | 30 + .../TypoScript/Page/ContentOnly.typoscript | 62 + .../TypoScript/Page/Export.typoscript | 25 + .../TypoScript/Page/Main.typoscript | 214 ++ .../TypoScript/constants.typoscript | 202 ++ .../Configuration/TypoScript/setup.typoscript | 125 ++ .../Configuration/Yaml/BaseSetup.yaml | 39 + .../Configuration/Yaml/FormEditorSetup.yaml | 0 .../Configuration/Yaml/FormEngineSetup.yaml | 0 .../Yaml/FormFinishersBackend.yaml | 111 + .../Yaml/FormFinishersFrontend.yaml | 35 + .../Configuration/Yaml/FormsBase.yaml | 42 + packages/ep_theme/Resources/Private/.htaccess | 1 + .../Assets/fonts/lato-v17-latin-300.woff | Bin 0 -> 30024 bytes .../Assets/fonts/lato-v17-latin-300.woff2 | Bin 0 -> 23248 bytes .../Assets/fonts/lato-v17-latin-700.woff | Bin 0 -> 28052 bytes .../Assets/fonts/lato-v17-latin-700.woff2 | Bin 0 -> 22992 bytes .../Assets/fonts/lato-v17-latin-900.woff | Bin 0 -> 27524 bytes .../Assets/fonts/lato-v17-latin-900.woff2 | Bin 0 -> 22572 bytes .../Assets/fonts/lato-v17-latin-regular.woff | Bin 0 -> 28660 bytes .../Assets/fonts/lato-v17-latin-regular.woff2 | Bin 0 -> 23484 bytes .../Assets/images/ajax-loader-blue.gif | Bin 0 -> 1737 bytes .../Assets/images/ajax-loader-white.gif | Bin 0 -> 1737 bytes .../Resources/Private/Assets/images/at.png | Bin 0 -> 133 bytes .../images/background/header-sommer.jpg | Bin 0 -> 30854 bytes .../images/background/header-winter.jpg | Bin 0 -> 24460 bytes .../Assets/images/background/pattern.jpg | Bin 0 -> 32480 bytes .../Assets/images/background/winter.jpg | Bin 0 -> 79967 bytes .../Private/Assets/images/badge_skipass.png | Bin 0 -> 7513 bytes .../Assets/images/badge_skipass_black.png | Bin 0 -> 3601 bytes .../Private/Assets/images/bg_login.jpg | Bin 0 -> 30995 bytes .../Resources/Private/Assets/images/ch.png | Bin 0 -> 242 bytes .../Assets/images/corner/corner_bottom_1.svg | 11 + .../Assets/images/corner/corner_bottom_3.svg | 13 + .../Assets/images/corner/corner_bottom_4.svg | 12 + .../images/corner/corner_bottom_deal.svg | 11 + .../Assets/images/corner/corner_top_1.svg | 14 + .../Assets/images/corner/corner_top_2.svg | 14 + .../Assets/images/corner/corner_top_3.svg | 14 + .../Assets/images/corner/corner_top_4.svg | 13 + .../Assets/images/corner/corner_top_5.svg | 13 + .../Assets/images/corner/corner_top_deal.svg | 13 + .../Assets/images/corner/corner_top_hilfe.svg | 15 + .../Resources/Private/Assets/images/de.png | Bin 0 -> 170 bytes .../Private/Assets/images/favicon_ep.ico | Bin 0 -> 4286 bytes .../Private/Assets/images/favicon_sbw.ico | Bin 0 -> 1150 bytes .../Private/Assets/images/favicon_ser.ico | Bin 0 -> 12014 bytes .../Private/Assets/images/favicon_sz.ico | Bin 0 -> 1150 bytes .../Private/Assets/images/favicon_uch.ico | Bin 0 -> 2238 bytes .../Private/Assets/images/flags/at.svg | 6 + .../Private/Assets/images/flags/ch.svg | 9 + .../Private/Assets/images/flags/de.svg | 5 + .../Private/Assets/images/flags/fr.svg | 7 + .../Private/Assets/images/flags/gb.svg | 7 + .../Resources/Private/Assets/images/fr.png | Bin 0 -> 181 bytes .../Resources/Private/Assets/images/gb.png | Bin 0 -> 4472 bytes .../Private/Assets/images/gb_grey.png | Bin 0 -> 3465 bytes .../Private/Assets/images/global_compact.png | Bin 0 -> 9028 bytes .../Assets/images/global_compact_weiss.png | Bin 0 -> 23487 bytes .../images/icons/apple-touch-icon-114x114.png | Bin 0 -> 7174 bytes .../images/icons/apple-touch-icon-120x120.png | Bin 0 -> 7507 bytes .../images/icons/apple-touch-icon-128x128.png | Bin 0 -> 8076 bytes .../images/icons/apple-touch-icon-144x144.png | Bin 0 -> 9023 bytes .../images/icons/apple-touch-icon-152x152.png | Bin 0 -> 9588 bytes .../images/icons/apple-touch-icon-180x180.png | Bin 0 -> 11517 bytes .../images/icons/apple-touch-icon-57x57.png | Bin 0 -> 3660 bytes .../images/icons/apple-touch-icon-60x60.png | Bin 0 -> 3854 bytes .../images/icons/apple-touch-icon-72x72.png | Bin 0 -> 4594 bytes .../images/icons/apple-touch-icon-76x76.png | Bin 0 -> 4795 bytes .../icons/apple-touch-icon-precomposed.png | Bin 0 -> 3660 bytes .../Assets/images/icons/apple-touch-icon.png | Bin 0 -> 3854 bytes .../Assets/images/icons/favicon-160x160.png | Bin 0 -> 10054 bytes .../Assets/images/icons/favicon-16x16.png | Bin 0 -> 1625 bytes .../Assets/images/icons/favicon-192x192.png | Bin 0 -> 12286 bytes .../Assets/images/icons/favicon-196x196.png | Bin 0 -> 12674 bytes .../Assets/images/icons/favicon-32x32.png | Bin 0 -> 2216 bytes .../Assets/images/icons/favicon-96x96.png | Bin 0 -> 6038 bytes .../Private/Assets/images/icons/favicon.gif | Bin 0 -> 6280 bytes .../Private/Assets/images/icons/favicon.ico | Bin 0 -> 4286 bytes .../Private/Assets/images/icons/favicon.png | Bin 0 -> 16602 bytes .../Private/Assets/images/icons/icon_ce.svg | 13 + .../Assets/images/icons/icon_select.png | Bin 0 -> 918 bytes .../icons/tx_eptheme_domain_model_lineup.gif | Bin 0 -> 172 bytes .../icons/tx_eptheme_domain_model_slide.gif | Bin 0 -> 172 bytes ...eme_gridelements_half_and_two_quarters.png | Bin 0 -> 145 bytes .../icons/tx_eptheme_gridelements_halves.png | Bin 0 -> 144 bytes .../tx_eptheme_gridelements_quarters.png | Bin 0 -> 150 bytes .../icons/tx_eptheme_gridelements_thirds.png | Bin 0 -> 143 bytes ...eme_gridelements_two_quarters_and_half.png | Bin 0 -> 152 bytes .../Private/Assets/images/icons_sprite.svg | 85 + .../Resources/Private/Assets/images/lazy.png | Bin 0 -> 2423 bytes .../Resources/Private/Assets/images/logo.svg | 1 + .../Assets/images/logo_boarderweek.svg | 673 ++++++ .../Private/Assets/images/logo_ser.svg | 58 + .../Private/Assets/images/logo_snowzone.svg | 228 ++ .../Assets/images/logo_snowzone_2019.svg | 1 + .../Private/Assets/images/logo_uni_champ.svg | 1951 +++++++++++++++++ .../Private/Assets/images/logo_wide.svg | 1 + .../Private/Assets/images/low-poly.jpg | Bin 0 -> 32480 bytes .../Private/Assets/images/polygon.jpg | Bin 0 -> 25756 bytes .../Private/Assets/images/pow_de.png | Bin 0 -> 12382 bytes .../Assets/images/signature_ep_team.png | Bin 0 -> 7291 bytes .../Resources/Private/Assets/js/_app.js | 46 + .../js/controllers/autocollapse_controller.js | 54 + .../js/controllers/calendar_controller.js | 68 + .../js/controllers/collapse_controller.js | 58 + .../js/controllers/datalayer_controller.js | 14 + .../js/controllers/datepicker_controller.js | 109 + .../js/controllers/daytrip_controller.js | 62 + .../Assets/js/controllers/faq_controller.js | 42 + .../controllers/form_debounce_controller.js | 25 + .../js/controllers/iframe_controller.js | 18 + .../js/controllers/lightbox_controller.js | 45 + .../controllers/mobile_search_controller.js | 80 + .../Assets/js/controllers/modal_controller.js | 22 + .../Assets/js/controllers/nav_controller.js | 36 + .../Assets/js/controllers/osm_controller.js | 78 + .../controllers/overlay_toggle_controller.js | 32 + .../controllers/price_calendar_controller.js | 185 ++ .../js/controllers/pricetable_controller.js | 106 + .../controllers/product_details_controller.js | 105 + .../js/controllers/scroll_to_controller.js | 11 + .../js/controllers/scroll_top_controller.js | 29 + .../js/controllers/search_controller.js | 59 + .../js/controllers/searchbar_controller.js | 82 + .../searchbar_toggle_controller.js | 14 + .../js/controllers/slider_controller.js | 106 + .../js/controllers/sticky_controller.js | 54 + .../Assets/js/controllers/tabs_controller.js | 34 + .../js/controllers/teasergrid_controller.js | 37 + .../js/controllers/watchlist_controller.js | 37 + .../controllers/watchlist_label_controller.js | 30 + .../watchlist_toggle_controller.js | 45 + .../Resources/Private/Assets/js/main.js | 5 + .../Private/Assets/js/mixins/use_config.js | 11 + .../Private/Assets/js/mixins/use_fetch.js | 19 + .../Resources/Private/Assets/scss/_base.scss | 15 + .../Private/Assets/scss/_components.scss | 15 + .../Private/Assets/scss/_utilities.scss | 40 + .../Private/Assets/scss/_vendor.scss | 143 ++ .../Private/Assets/scss/base/_fonts.scss | 43 + .../Private/Assets/scss/base/_images.scss | 5 + .../Assets/scss/base/_rte-content.scss | 86 + .../Private/Assets/scss/base/_typography.scss | 49 + .../Assets/scss/components/_background.scss | 21 + .../Assets/scss/components/_badges.scss | 51 + .../Assets/scss/components/_buttons.scss | 100 + .../Assets/scss/components/_calendar.scss | 35 + .../Assets/scss/components/_contact-bar.scss | 37 + .../Assets/scss/components/_forms.scss | 22 + .../Assets/scss/components/_herounit.scss | 20 + .../Private/Assets/scss/components/_misc.scss | 11 + .../Assets/scss/components/_searchbar.scss | 11 + .../Assets/scss/components/_teaser.scss | 73 + .../Assets/scss/components/_topnav.scss | 20 + .../Assets/scss/components/_travelinfo.scss | 19 + .../Resources/Private/Assets/scss/main.scss | 18 + .../Resources/Private/Assets/scss/rte.scss | 30 + .../Private/Forms/AdresscheckForm.form.yaml | 144 ++ .../Private/Forms/AdresscheckRemove.form.yaml | 118 + .../Private/Forms/ApplicationForm.form.yaml | 378 ++++ .../Private/Forms/ContactForm.form.yaml | 148 ++ .../Private/Language/de.locallang.xlf | 113 + .../Resources/Private/Language/locallang.xlf | 132 ++ .../Private/Language/locallang_db.xlf | 74 + .../Private/Language/locallang_mod.xlf | 17 + .../Private/Layouts/Email/SystemEmail.html | 388 ++++ .../Layouts/FluidStyledContent/Bare.html | 1 + .../Layouts/FluidStyledContent/Default.html | 4 + .../Private/Layouts/Form/Default.html | 1 + .../Private/Layouts/Gridelements/Default.html | 2 + .../Private/Layouts/News/Detail.html | 7 + .../Private/Layouts/Page/Backend.html | 1 + .../Private/Layouts/Page/Detail.html | 23 + .../Private/Layouts/Page/Frontpage.html | 31 + .../Private/Layouts/Page/FrontpageEvent.html | 22 + .../Resources/Private/Layouts/Page/Popup.html | 7 + .../Private/Layouts/Page/Search.html | 18 + .../Private/Layouts/Page/Subpage.html | 31 + .../Layouts/Page/SubpageSidebarLeft.html | 35 + .../Layouts/Page/SubpageSidebarLeftEvent.html | 29 + .../Layouts/Page/SubpageSidebarRight.html | 35 + .../Page/SubpageSidebarRightEvent.html | 29 + .../Private/Layouts/Page/Travelinfo.html | 28 + .../Private/Layouts/Product/Default.html | 1 + .../Resources/Private/Partials/Badge.html | 29 + .../Resources/Private/Partials/Chatbot.html | 58 + .../Private/Partials/City/Details.html | 154 ++ .../Private/Partials/City/Teaser.html | 52 + .../Private/Partials/Concept/Teaser.html | 51 + .../Resources/Private/Partials/Config.html | 10 + .../Private/Partials/ContactBar.html | 111 + .../Private/Partials/ContactForm/Error.html | 18 + .../Private/Partials/ContactForm/Success.html | 19 + .../Private/Partials/Disrupter/Large.html | 63 + .../Partials/Disrupter/LastChance.html | 29 + .../Private/Partials/Disrupter/Outlines.html | 47 + .../Private/Partials/Disrupter/Reminder.html | 30 + .../Private/Partials/Disrupter/Small.html | 42 + .../Private/Partials/Disturber/Large.html | 64 + .../Partials/Disturber/LastChance.html | 21 + .../Private/Partials/Disturber/Reminder.html | 32 + .../Private/Partials/Disturber/Small.html | 42 + .../Private/Partials/EventDisturber.html | 34 + .../Resources/Private/Partials/EventHero.html | 75 + .../Resources/Private/Partials/Facts.html | 29 + .../Resources/Private/Partials/Faq.html | 62 + .../Resources/Private/Partials/FaqGroup.html | 40 + .../Resources/Private/Partials/Footer.html | 191 ++ .../Private/Partials/Form/Captcha.html | 38 + .../Private/Partials/Form/Checkbox.html | 30 + .../Private/Partials/Form/ContentElement.html | 21 + .../Resources/Private/Partials/Form/Date.html | 14 + .../Private/Partials/Form/Email.html | 14 + .../Private/Partials/Form/Field/Field.html | 18 + .../Private/Partials/Form/Fieldset.html | 12 + .../Partials/Form/Form/Navigation.html | 28 + .../Private/Partials/Form/LinkedCheckbox.html | 19 + .../Private/Partials/Form/MultiCheckbox.html | 35 + .../Private/Partials/Form/MultiSelect.html | 33 + .../Private/Partials/Form/Number.html | 14 + .../Private/Partials/Form/RadioButton.html | 22 + .../Private/Partials/Form/SingleSelect.html | 30 + .../Private/Partials/Form/StaticText.html | 11 + .../Private/Partials/Form/Telephone.html | 14 + .../Resources/Private/Partials/Form/Text.html | 13 + .../Private/Partials/Form/Textarea.html | 15 + .../Private/Partials/Header/Header.html | 67 + .../Private/Partials/Header/Standalone.html | 14 + .../Private/Partials/HeaderSlider.html | 87 + .../Private/Partials/HeaderSliderSmall.html | 79 + .../Private/Partials/Hotel/Details.html | 204 ++ .../Private/Partials/Hotel/Teaser.html | 53 + .../Private/Partials/ImageGallery.html | 64 + .../Private/Partials/Journey/Details.html | 33 + .../Resources/Private/Partials/Mainnav.html | 168 ++ .../Resources/Private/Partials/Map/Osm.html | 10 + .../Resources/Private/Partials/Mobilenav.html | 171 ++ .../Partials/News/Detail/MediaContainer.html | 51 + .../Private/Partials/News/List/Item.html | 70 + .../Private/Partials/NotBookable.html | 11 + .../Resources/Private/Partials/Officetip.html | 26 + .../Private/Partials/Pricetable.html | 166 ++ .../Private/Partials/Product/Details.html | 83 + .../Partials/Product/HeaderDetail.html | 88 + .../Private/Partials/Product/Pickups.html | 81 + .../Partials/Product/PriceCalendar.html | 42 + .../Private/Partials/Product/Teaser.html | 136 ++ .../Resources/Private/Partials/Rating.html | 17 + .../Private/Partials/Region/Details.html | 196 ++ .../Private/Partials/Region/Teaser.html | 52 + .../Private/Partials/Region/Webcam.html | 24 + .../Private/Partials/RegionAward.html | 20 + .../Private/Partials/RegionMaps.html | 30 + .../Private/Partials/Regioninfo.html | 38 + .../Private/Partials/Search/FilterPanel.html | 348 +++ .../Private/Partials/Search/Main.html | 35 + .../Private/Partials/Search/MobileSearch.html | 34 + .../Private/Partials/Search/Result.html | 114 + .../Private/Partials/Snowreport.html | 12 + .../Private/Partials/SubpageHeader.html | 68 + .../Resources/Private/Partials/Teamer.html | 23 + .../Private/Partials/Teaser/OneColumn.html | 34 + .../Private/Partials/Teaser/TwoColumn.html | 27 + .../Private/Partials/TeaserImage.html | 63 + .../Private/Partials/TeaserWideImage.html | 27 + .../Private/Partials/Textmedia/Default.html | 18 + .../Private/Partials/Textmedia/LinkPanel.html | 74 + .../Partials/Textmedia/Partnerlogos.html | 37 + .../Resources/Private/Partials/Topnav.html | 50 + .../Private/Partials/Travelinfo.html | 532 +++++ .../Templates/AjaxCalendar/PriceCalendar.html | 76 + .../Templates/AjaxCalendar/PriceConfig.html | 1 + .../Templates/AjaxCalendar/PriceTable.html | 79 + .../Private/Templates/AjaxCalendar/Range.html | 57 + .../Templates/AjaxFilterPanel/Index.html | 32 + .../Templates/AjaxSearch/Searchresult.html | 19 + .../Templates/AjaxTable/Datestable.html | 335 +++ .../Templates/AjaxTable/DaytripRooms.html | 126 ++ .../Templates/AjaxTable/EventPricetable.html | 87 + .../Templates/AjaxTable/Pricetable.html | 325 +++ .../Templates/AjaxTable/PricetableHtml.html | 10 + .../Templates/AjaxTeaserPopup/Concept.html | 43 + .../Private/Templates/AjaxWatchlist/List.html | 44 + .../Private/Templates/Backend/ListPages.html | 54 + .../Private/Templates/City/Detail.html | 31 + .../Private/Templates/City/Teasergroup.html | 46 + .../Private/Templates/Concept/Detail.html | 123 ++ .../Templates/Concept/Teasergroup.html | 46 + .../Private/Templates/Container/Quarters.html | 60 + .../Templates/Container/Teasergroup.html | 60 + .../Private/Templates/Country/Detail.html | 130 ++ .../Private/Templates/Email/Autoreply.html | 15 + .../Private/Templates/Email/ContactForm.html | 65 + .../Private/Templates/Email/GroupsPrice.html | 157 ++ .../Private/Templates/Faq/Detail.html | 13 + .../Templates/FluidStyledContent/Button.html | 16 + .../FluidStyledContent/Calendar.html | 30 + .../Templates/FluidStyledContent/Contact.html | 31 + .../FluidStyledContent/DealOfTheWeek.html | 47 + .../FluidStyledContent/Disrupter.html | 26 + .../FluidStyledContent/Disturber.html | 25 + .../FluidStyledContent/EventPricetable.html | 29 + .../FluidStyledContent/FaqOverview.html | 41 + .../Templates/FluidStyledContent/Gmap.html | 19 + .../Templates/FluidStyledContent/Header.html | 15 + .../Templates/FluidStyledContent/Html.html | 12 + .../Templates/FluidStyledContent/Lineup.html | 71 + .../FluidStyledContent/MenuSection.html | 34 + .../FluidStyledContent/MenuSectionPages.html | 35 + .../FluidStyledContent/MenuSubpages.html | 20 + .../FluidStyledContent/NewsletterForm.html | 13 + .../FluidStyledContent/Shortcut.html | 10 + .../Templates/FluidStyledContent/Teaser.html | 19 + .../FluidStyledContent/TeaserBookingLink.html | 31 + .../FluidStyledContent/TeaserCity.html | 13 + .../FluidStyledContent/TeaserConcept.html | 13 + .../FluidStyledContent/TeaserCountry.html | 37 + .../FluidStyledContent/TeaserHotel.html | 13 + .../FluidStyledContent/TeaserProduct.html | 33 + .../FluidStyledContent/TeaserRegion.html | 13 + .../Templates/FluidStyledContent/Text.html | 14 + .../FluidStyledContent/Textmedia.html | 30 + .../Templates/FluidStyledContent/Textpic.html | 30 + .../FluidStyledContent/YoutubeVideo.html | 25 + .../Private/Templates/Form/Form.html | 22 + .../Private/Templates/Form/Render.html | 10 + .../Gridelements/HalfAndTwoQuarters.html | 30 + .../Templates/Gridelements/Halves.html | 27 + .../Templates/Gridelements/Quarters.html | 21 + .../Templates/Gridelements/Thirds.html | 20 + .../Gridelements/TwoQuartersAndHalf.html | 30 + .../Private/Templates/GroupsPrice/Index.html | 37 + .../Hotel/DateIndependentTeasergroup.html | 27 + .../Private/Templates/Hotel/Detail.html | 37 + .../Private/Templates/Hotel/DetailEvent.html | 39 + .../Private/Templates/Hotel/Teasergroup.html | 54 + .../Private/Templates/Map/General.html | 15 + .../Private/Templates/News/News/Detail.html | 91 + .../Private/Templates/News/News/List.html | 51 + .../Private/Templates/News/Tag/List.html | 24 + .../ViewHelpers/Widget/Paginate/Index.html | 84 + .../Private/Templates/Newsletter/Index.html | 46 + .../Newsletter/SubscriptionForm.html | 104 + .../Newsletter/SubscriptionFormSubmit.html | 23 + .../Private/Templates/Page/Detail.html | 15 + .../Private/Templates/Page/Frontpage.html | 11 + .../Templates/Page/FrontpageEvent.html | 11 + .../Private/Templates/Page/Popup.html | 11 + .../Private/Templates/Page/Search.html | 11 + .../Private/Templates/Page/Subpage.html | 15 + .../Templates/Page/SubpageSidebarLeft.html | 23 + .../Page/SubpageSidebarLeftEvent.html | 23 + .../Templates/Page/SubpageSidebarRight.html | 23 + .../Page/SubpageSidebarRightEvent.html | 23 + .../Private/Templates/Page/Travelinfo.html | 11 + .../Private/Templates/Password/Index.html | 15 + .../Templates/PreviewRenderer/Disrupter.html | 11 + .../Templates/PreviewRenderer/Disturber.html | 24 + .../Templates/PreviewRenderer/Gmap.html | 11 + .../Templates/PreviewRenderer/Lineup.html | 15 + .../PreviewRenderer/TeaserBookingLink.html | 23 + .../Templates/PreviewRenderer/TeaserCity.html | 23 + .../PreviewRenderer/TeaserConcept.html | 23 + .../PreviewRenderer/TeaserCountry.html | 23 + .../PreviewRenderer/TeaserHotel.html | 23 + .../PreviewRenderer/TeaserProduct.html | 23 + .../PreviewRenderer/TeaserRegion.html | 23 + .../Private/Templates/Pricetable/Event.html | 15 + .../Private/Templates/Product/Detail.html | 372 ++++ .../Private/Templates/Product/HotelList.html | 77 + .../Private/Templates/Product/Pricetable.html | 13 + .../Templates/Product/Teasergroup.html | 118 + .../Private/Templates/Region/Detail.html | 34 + .../Private/Templates/Region/Teasergroup.html | 46 + .../Private/Templates/Reseller/Copypaste.html | 345 +++ .../Private/Templates/Reseller/List.html | 33 + .../Templates/Search/Searchresult.html | 29 + .../Private/Templates/Searchbar/Index.html | 279 +++ .../Private/Templates/Snowreport/List.html | 53 + .../Private/Templates/Team/List.html | 49 + .../Private/Templates/Team/Show.html | 48 + .../Templates/Travelinfo/TravelCode.html | 11 + .../Templates/Travelinfo/TravelInfo.html | 11 + .../Private/Templates/Watchlist/List.html | 23 + packages/ep_theme/composer.json | 32 + packages/ep_theme/ext_emconf.php | 21 + packages/ep_theme/ext_icon.gif | Bin 0 -> 1425 bytes packages/ep_theme/ext_localconf.php | 63 + packages/ep_theme/ext_tables.php | 39 + packages/ep_theme/ext_tables.sql | 225 ++ 778 files changed, 67000 insertions(+), 2 deletions(-) create mode 100644 packages/ep_products/Classes/BpnConnect/ApiClient.php create mode 100644 packages/ep_products/Classes/BpnConnect/ApiException.php create mode 100644 packages/ep_products/Classes/Command/ContingentCommand.php create mode 100644 packages/ep_products/Classes/Command/DateCommandController.php create mode 100644 packages/ep_products/Classes/Command/ExportCommandController.php create mode 100644 packages/ep_products/Classes/Command/GroupsSwissCommand.php create mode 100644 packages/ep_products/Classes/Command/SnowreportCommandController.php create mode 100644 packages/ep_products/Classes/ContentFeed/ContentFeedApi.php create mode 100644 packages/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php create mode 100644 packages/ep_products/Classes/ContentFeed/ManifestBuilder.php create mode 100644 packages/ep_products/Classes/ContentFeed/PageTree.php create mode 100644 packages/ep_products/Classes/ContentFeed/RecordFinder.php create mode 100644 packages/ep_products/Classes/ContentFeed/RecordSerializer.php create mode 100644 packages/ep_products/Classes/ContentFeed/RteLinkResolver.php create mode 100644 packages/ep_products/Classes/ContentFeed/UrlResolver.php create mode 100644 packages/ep_products/Classes/Controller/AjaxCalendarController.php create mode 100644 packages/ep_products/Classes/Controller/AjaxContingentController.php create mode 100644 packages/ep_products/Classes/Controller/AjaxDateController.php create mode 100644 packages/ep_products/Classes/Controller/AjaxFilterPanelController.php create mode 100644 packages/ep_products/Classes/Controller/AjaxSearchController.php create mode 100644 packages/ep_products/Classes/Controller/AjaxTableController.php create mode 100644 packages/ep_products/Classes/Controller/AjaxTeaserPopupController.php create mode 100644 packages/ep_products/Classes/Controller/AjaxWatchlistController.php create mode 100644 packages/ep_products/Classes/Controller/BackendController.php create mode 100644 packages/ep_products/Classes/Controller/CityController.php create mode 100644 packages/ep_products/Classes/Controller/ConceptController.php create mode 100644 packages/ep_products/Classes/Controller/CountryController.php create mode 100644 packages/ep_products/Classes/Controller/FaqController.php create mode 100644 packages/ep_products/Classes/Controller/HotelController.php create mode 100644 packages/ep_products/Classes/Controller/IcalController.php create mode 100644 packages/ep_products/Classes/Controller/MapController.php create mode 100644 packages/ep_products/Classes/Controller/NewsletterController.php create mode 100644 packages/ep_products/Classes/Controller/PricetableController.php create mode 100644 packages/ep_products/Classes/Controller/ProductController.php create mode 100644 packages/ep_products/Classes/Controller/RegionController.php create mode 100644 packages/ep_products/Classes/Controller/ResellerController.php create mode 100644 packages/ep_products/Classes/Controller/SearchController.php create mode 100644 packages/ep_products/Classes/Controller/SearchbarController.php create mode 100644 packages/ep_products/Classes/Controller/SnowreportController.php create mode 100644 packages/ep_products/Classes/Controller/TeamController.php create mode 100644 packages/ep_products/Classes/Controller/TravelinfoController.php create mode 100644 packages/ep_products/Classes/Controller/WatchlistController.php create mode 100644 packages/ep_products/Classes/CsvFormatter/FacebookCatalogCsvFormatter.php create mode 100644 packages/ep_products/Classes/CsvFormatter/GoogleBusinessFeedCsvFormatter.php create mode 100644 packages/ep_products/Classes/Domain/Model/AbstractDestination.php create mode 100644 packages/ep_products/Classes/Domain/Model/Badge.php create mode 100644 packages/ep_products/Classes/Domain/Model/City.php create mode 100644 packages/ep_products/Classes/Domain/Model/Concept.php create mode 100644 packages/ep_products/Classes/Domain/Model/Contingent.php create mode 100644 packages/ep_products/Classes/Domain/Model/Country.php create mode 100644 packages/ep_products/Classes/Domain/Model/Date.php create mode 100644 packages/ep_products/Classes/Domain/Model/Dto/FilterOptions.php create mode 100644 packages/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php create mode 100644 packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php create mode 100644 packages/ep_products/Classes/Domain/Model/Fact.php create mode 100644 packages/ep_products/Classes/Domain/Model/Faq.php create mode 100644 packages/ep_products/Classes/Domain/Model/GroupsPriceBoard.php create mode 100644 packages/ep_products/Classes/Domain/Model/GroupsPriceConfig.php create mode 100644 packages/ep_products/Classes/Domain/Model/GroupsPriceOption.php create mode 100644 packages/ep_products/Classes/Domain/Model/Hotel.php create mode 100644 packages/ep_products/Classes/Domain/Model/Infobox.php create mode 100644 packages/ep_products/Classes/Domain/Model/Journey.php create mode 100644 packages/ep_products/Classes/Domain/Model/MatchCode.php create mode 100644 packages/ep_products/Classes/Domain/Model/Officetip.php create mode 100644 packages/ep_products/Classes/Domain/Model/Product.php create mode 100644 packages/ep_products/Classes/Domain/Model/Region.php create mode 100644 packages/ep_products/Classes/Domain/Model/Reseller.php create mode 100644 packages/ep_products/Classes/Domain/Model/Room.php create mode 100644 packages/ep_products/Classes/Domain/Model/SearchResult.php create mode 100644 packages/ep_products/Classes/Domain/Model/Snowreport.php create mode 100644 packages/ep_products/Classes/Domain/Model/Teammember.php create mode 100644 packages/ep_products/Classes/Domain/Model/TeaserInterface.php create mode 100644 packages/ep_products/Classes/Domain/Model/Travelinfo.php create mode 100644 packages/ep_products/Classes/Domain/Model/TravelinfoLink.php create mode 100644 packages/ep_products/Classes/Domain/Model/TravelinfoTeaser.php create mode 100644 packages/ep_products/Classes/Domain/Model/Webcam.php create mode 100644 packages/ep_products/Classes/Domain/Repository/AbstractRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/CityRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/ConceptRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/ContingentRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/CountryRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/DateRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/FaqRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/GroupsPriceOptionRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/HotelRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/InfoboxRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/MatchCodeRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/ProductRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/RegionRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/ResellerRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/RoomRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/SnowreportRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/TravelinfoRepository.php create mode 100644 packages/ep_products/Classes/Domain/Repository/WebcamRepository.php create mode 100644 packages/ep_products/Classes/Domain/Serialization/City.php create mode 100644 packages/ep_products/Classes/Domain/Serialization/Date.php create mode 100644 packages/ep_products/Classes/Domain/Serialization/Hotel.php create mode 100644 packages/ep_products/Classes/Domain/Serialization/Product.php create mode 100644 packages/ep_products/Classes/Domain/Serialization/Region.php create mode 100644 packages/ep_products/Classes/Domain/Serialization/Room.php create mode 100644 packages/ep_products/Classes/Domain/Serialization/Services.php create mode 100644 packages/ep_products/Classes/EpTeam/ApiClient.php create mode 100644 packages/ep_products/Classes/EpTeam/ApiException.php create mode 100644 packages/ep_products/Classes/EventListener/SystemInformationToolbarListener.php create mode 100644 packages/ep_products/Classes/Hooks/PageLayoutView.php create mode 100644 packages/ep_products/Classes/Hooks/Tcemain.php create mode 100644 packages/ep_products/Classes/Http/ApiKeyAuthenticator.php create mode 100644 packages/ep_products/Classes/Log/Writer.php create mode 100644 packages/ep_products/Classes/Middleware/ApiMiddleware.php create mode 100644 packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php create mode 100644 packages/ep_products/Classes/Middleware/StagingEnvironmentMiddleware.php create mode 100644 packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php create mode 100644 packages/ep_products/Classes/MyEP/ApiClient.php create mode 100644 packages/ep_products/Classes/MyEP/ApiException.php create mode 100644 packages/ep_products/Classes/Routing/Aspect/PassthroughMapper.php create mode 100644 packages/ep_products/Classes/Service/AbstractImageService.php create mode 100644 packages/ep_products/Classes/Service/ApiImportSource.php create mode 100644 packages/ep_products/Classes/Service/ContingentDataService.php create mode 100644 packages/ep_products/Classes/Service/ContingentImportService.php create mode 100644 packages/ep_products/Classes/Service/DateImportService.php create mode 100644 packages/ep_products/Classes/Service/DateService.php create mode 100644 packages/ep_products/Classes/Service/EmailService.php create mode 100644 packages/ep_products/Classes/Service/FilterService.php create mode 100644 packages/ep_products/Classes/Service/FilterSettingsEncoder.php create mode 100644 packages/ep_products/Classes/Service/GroupsSwissService.php create mode 100644 packages/ep_products/Classes/Service/HotelDataService.php create mode 100644 packages/ep_products/Classes/Service/HotelImageService.php create mode 100644 packages/ep_products/Classes/Service/IcalService.php create mode 100644 packages/ep_products/Classes/Service/ImportTimestampService.php create mode 100644 packages/ep_products/Classes/Service/LabelService.php create mode 100644 packages/ep_products/Classes/Service/LocalImportSource.php create mode 100644 packages/ep_products/Classes/Service/LogService.php create mode 100644 packages/ep_products/Classes/Service/MyEpImageService.php create mode 100644 packages/ep_products/Classes/Service/MyEpUrlResolverService.php create mode 100644 packages/ep_products/Classes/Service/PickupOverviewService.php create mode 100644 packages/ep_products/Classes/Service/ProductDataService.php create mode 100644 packages/ep_products/Classes/Service/ProductImageService.php create mode 100644 packages/ep_products/Classes/Service/ProductImport.php create mode 100644 packages/ep_products/Classes/Service/ProductImportSource.php create mode 100644 packages/ep_products/Classes/Service/ProductImportSourceFactory.php create mode 100644 packages/ep_products/Classes/Service/ResellerDataService.php create mode 100644 packages/ep_products/Classes/Service/RoomTypeResolver.php create mode 100644 packages/ep_products/Classes/Service/SearchResultService.php create mode 100644 packages/ep_products/Classes/Service/SftpImportException.php create mode 100644 packages/ep_products/Classes/Service/SftpImportSource.php create mode 100644 packages/ep_products/Classes/Service/SnowreportImportService.php create mode 100644 packages/ep_products/Classes/Service/XmlProductReader.php create mode 100644 packages/ep_products/Classes/Task/ImportContingentsTask.php create mode 100644 packages/ep_products/Classes/Task/ImportDatesTask.php create mode 100644 packages/ep_products/Classes/Task/ImportSnowreportTask.php create mode 100644 packages/ep_products/Classes/Traits/DbConnectionTrait.php create mode 100644 packages/ep_products/Classes/Traits/RequestArgumentTypeConversionTrait.php create mode 100644 packages/ep_products/Classes/Updates/TeamSlugUpdater.php create mode 100644 packages/ep_products/Classes/UserFunc/TcaProcFunc.php create mode 100644 packages/ep_products/Classes/Utility/BookingUrlUtility.php create mode 100644 packages/ep_products/Classes/Utility/DateUtility.php create mode 100644 packages/ep_products/Classes/Utility/HotelIconUtility.php create mode 100644 packages/ep_products/Classes/Utility/SerializationUtility.php create mode 100644 packages/ep_products/Classes/Utility/SettingsUtility.php create mode 100644 packages/ep_products/Classes/Utility/TypeConversionUtility.php create mode 100644 packages/ep_products/Configuration/Commands.php create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_city_detail.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_city_teaser.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_concept_detail.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_country.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_faq.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_hotel_detail.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_hotel_detail_event.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser_date_independent.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_maps.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_pricetable.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_product_detail.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_product_pricetable.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_product_teaser.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_region_detail.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_region_teaser.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_snowreport.xml create mode 100644 packages/ep_products/Configuration/FlexForms/flexform_teammembers.xml create mode 100644 packages/ep_products/Configuration/RequestMiddlewares.php create mode 100644 packages/ep_products/Configuration/Services.yaml create mode 100644 packages/ep_products/Configuration/TCA/Overrides/sys_domain.php create mode 100644 packages/ep_products/Configuration/TCA/Overrides/sys_template.php create mode 100644 packages/ep_products/Configuration/TCA/Overrides/tt_content.php create mode 100644 packages/ep_products/Configuration/TCA/Overrides/tx_epproducts_domain_model_teammember.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_badge.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_city.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_concept.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_contingent.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_country.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_date.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_fact.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_faq.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceconfig.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel_mapping.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_infobox.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_journey.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_matchcode.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_officetip.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_product.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_region.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_reseller.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_snowreport.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_staticsearchparams.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_teammember.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfolink.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfoteaser.php create mode 100644 packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_webcam.php create mode 100644 packages/ep_products/Configuration/TypoScript/constants.typoscript create mode 100644 packages/ep_products/Configuration/TypoScript/setup.typoscript create mode 100644 packages/ep_products/README.md create mode 100644 packages/ep_products/Resources/Private/.htaccess create mode 100644 packages/ep_products/Resources/Private/Language/locallang.xlf create mode 100644 packages/ep_products/Resources/Private/Language/locallang_mod.xlf create mode 100644 packages/ep_products/Resources/Private/Layouts/Backend.html create mode 100644 packages/ep_products/Resources/Private/Templates/Backend/Index.html create mode 100644 packages/ep_products/Resources/Public/Icons/logo.svg create mode 100644 packages/ep_products/Resources/Public/Icons/relation.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_badge.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_city.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_concept.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_contingent.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_country.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_date.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_fact.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_faq.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_hotel.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_journey.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_matchcode.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_officetip.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_product.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_region.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_reseller.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_room.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_roommapping.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_snowreport.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_teammember.gif create mode 100644 packages/ep_products/Resources/Public/Icons/tx_epproducts_domain_model_webcam.gif create mode 100644 packages/ep_products/composer.json create mode 100644 packages/ep_products/ext_conf_template.txt create mode 100644 packages/ep_products/ext_emconf.php create mode 100644 packages/ep_products/ext_icon.gif create mode 100644 packages/ep_products/ext_localconf.php create mode 100644 packages/ep_products/ext_tables.php create mode 100644 packages/ep_products/ext_tables.sql create mode 100755 packages/ep_theme/Classes/.htaccess create mode 100644 packages/ep_theme/Classes/Controller/BackendController.php create mode 100644 packages/ep_theme/Classes/DataProcessing/ContextProcessor.php create mode 100644 packages/ep_theme/Classes/DataProcessing/FlexFormProcessor.php create mode 100644 packages/ep_theme/Classes/DataProcessing/ProductRatingProcessor.php create mode 100644 packages/ep_theme/Classes/DataProcessing/SliderProcessor.php create mode 100644 packages/ep_theme/Classes/DataProcessing/TeaserImageProcessor.php create mode 100644 packages/ep_theme/Classes/DataProcessing/TeaserProcessor.php create mode 100644 packages/ep_theme/Classes/Domain/Model/Dto/ContactForm.php create mode 100644 packages/ep_theme/Classes/Domain/Model/Page.php create mode 100644 packages/ep_theme/Classes/Domain/Repository/PageRepository.php create mode 100644 packages/ep_theme/Classes/Exception/ConfigurationMissingException.php create mode 100644 packages/ep_theme/Classes/Exception/TemplateFileMissingException.php create mode 100644 packages/ep_theme/Classes/Form/AbstractMappedFinisher.php create mode 100644 packages/ep_theme/Classes/Form/AddReferrerFinisher.php create mode 100644 packages/ep_theme/Classes/Form/ApplicationApiFinisher.php create mode 100644 packages/ep_theme/Classes/Form/BpnApiFinisher.php create mode 100644 packages/ep_theme/Classes/Form/WebhookFinisher.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/AbstractPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/DisrupterPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/DisturberPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/GmapPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/LineupPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserBookingLinkPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserCityPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserConceptPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserCountryPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserHotelPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserProductPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/PageLayoutView/TeaserRegionPreviewRenderer.php create mode 100644 packages/ep_theme/Classes/Hooks/Tcemain.php create mode 100644 packages/ep_theme/Classes/MetaTag/OpenGraphMetaTagManager.php create mode 100644 packages/ep_theme/Classes/Middleware/ReferrerMiddleware.php create mode 100644 packages/ep_theme/Classes/Service/EmailService.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/ArgumentPrefixViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/Calendar/ContingentViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/ComfortCategoryViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/ContainerViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/ContextClassesViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/DateRangeViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/FilterSettingsEncoderViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/GmapsUrlViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/HotelCategoriesViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/HotelCategoryViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/IconViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/MyEpUrlViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/NightsOptionsViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/PriceRangeViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/PriceViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/RatingViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/Stimulus/AbstractStimulusViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/Stimulus/ActionViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/Stimulus/ControllerViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/Stimulus/TargetViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/ThemeClassesViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/Uri/AjaxViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/Uri/BookingViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/Uri/ExternalImageViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/Uri/ProductViewHelper.php create mode 100644 packages/ep_theme/Classes/ViewHelpers/Uri/VideoViewHelper.php create mode 100755 packages/ep_theme/Configuration/.htaccess create mode 100644 packages/ep_theme/Configuration/Extbase/Persistence/Classes.php create mode 100644 packages/ep_theme/Configuration/FlexForms/flexform_disturber.xml create mode 100644 packages/ep_theme/Configuration/RTE/Default.yaml create mode 100644 packages/ep_theme/Configuration/RTE/Minimal.yaml create mode 100644 packages/ep_theme/Configuration/RequestMiddlewares.php create mode 100644 packages/ep_theme/Configuration/Services.yaml create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/pages.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/sys_file.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/sys_template.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_button.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_calendar.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_contact.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_container.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_deal_of_the_week.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_disrupter.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_disturber.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_event_pricetable.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_faq_overview.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_gmap.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_lineup.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_nlform.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_teaser.php create mode 100644 packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_ytvideo.php create mode 100644 packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_addresscheck.php create mode 100644 packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_disrupter.php create mode 100644 packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_lineup.php create mode 100644 packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_slide.php create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Extensions/GridElements.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Main.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/SHARED.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Detail.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Frontpage.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/FrontpageEvent.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Popup.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Search.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Subpage.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarLeft.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarLeftEvent.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarRight.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/SubpageSidebarRightEvent.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/WebLayout/BackendLayouts/Travelinfo.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/Mod/Wizards/NewContentElement.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/RTE.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/TCEFORM.tsconfig create mode 100644 packages/ep_theme/Configuration/TSconfig/Page/TCEMAIN.tsconfig create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/button.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/calendar.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/contact.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/container.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/disrupter.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/disturber.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/event_pricetable.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/faq_overview.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/gmap.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/lineup.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/list.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/nlform.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/teaser.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/ContentElements/ytvideo.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Extensions/EpProducts.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Extensions/FluidStyledContent.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Extensions/Form.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Extensions/GridElements.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Extensions/News.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Extensions/Replacer.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Extensions/Seo.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Extensions/Typo3Encore.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Library/Breadcrumb.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Library/DynamicContent.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Library/ParseFunc.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Library/SearchBar.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Library/Sitemap.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Page/Ajax.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Page/ContentOnly.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Page/Export.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/Page/Main.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/constants.typoscript create mode 100644 packages/ep_theme/Configuration/TypoScript/setup.typoscript create mode 100644 packages/ep_theme/Configuration/Yaml/BaseSetup.yaml create mode 100644 packages/ep_theme/Configuration/Yaml/FormEditorSetup.yaml create mode 100644 packages/ep_theme/Configuration/Yaml/FormEngineSetup.yaml create mode 100644 packages/ep_theme/Configuration/Yaml/FormFinishersBackend.yaml create mode 100644 packages/ep_theme/Configuration/Yaml/FormFinishersFrontend.yaml create mode 100644 packages/ep_theme/Configuration/Yaml/FormsBase.yaml create mode 100755 packages/ep_theme/Resources/Private/.htaccess create mode 100644 packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff create mode 100644 packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff2 create mode 100644 packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-700.woff create mode 100644 packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-700.woff2 create mode 100644 packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-900.woff create mode 100644 packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-900.woff2 create mode 100644 packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-regular.woff create mode 100644 packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-regular.woff2 create mode 100644 packages/ep_theme/Resources/Private/Assets/images/ajax-loader-blue.gif create mode 100644 packages/ep_theme/Resources/Private/Assets/images/ajax-loader-white.gif create mode 100755 packages/ep_theme/Resources/Private/Assets/images/at.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/background/header-sommer.jpg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/background/header-winter.jpg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/background/pattern.jpg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/background/winter.jpg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/badge_skipass.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/badge_skipass_black.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/bg_login.jpg create mode 100755 packages/ep_theme/Resources/Private/Assets/images/ch.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_1.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_3.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_4.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_deal.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_1.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_2.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_3.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_4.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_5.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_deal.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_hilfe.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/de.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/favicon_ep.ico create mode 100644 packages/ep_theme/Resources/Private/Assets/images/favicon_sbw.ico create mode 100644 packages/ep_theme/Resources/Private/Assets/images/favicon_ser.ico create mode 100644 packages/ep_theme/Resources/Private/Assets/images/favicon_sz.ico create mode 100644 packages/ep_theme/Resources/Private/Assets/images/favicon_uch.ico create mode 100644 packages/ep_theme/Resources/Private/Assets/images/flags/at.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/flags/ch.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/flags/de.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/flags/fr.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/flags/gb.svg create mode 100755 packages/ep_theme/Resources/Private/Assets/images/fr.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/gb.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/gb_grey.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/global_compact.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/global_compact_weiss.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-114x114.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-120x120.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-128x128.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-144x144.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-152x152.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-180x180.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-57x57.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-60x60.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-72x72.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-76x76.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-precomposed.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/favicon-160x160.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/favicon-16x16.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/favicon-192x192.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/favicon-196x196.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/favicon-32x32.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/favicon-96x96.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/favicon.gif create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/favicon.ico create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/favicon.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/icon_ce.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/icon_select.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_lineup.gif create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_slide.gif create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_half_and_two_quarters.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_halves.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_quarters.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_thirds.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_two_quarters_and_half.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/icons_sprite.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/lazy.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/logo.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/logo_boarderweek.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/logo_ser.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/logo_snowzone.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/logo_snowzone_2019.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/logo_uni_champ.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/logo_wide.svg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/low-poly.jpg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/polygon.jpg create mode 100644 packages/ep_theme/Resources/Private/Assets/images/pow_de.png create mode 100644 packages/ep_theme/Resources/Private/Assets/images/signature_ep_team.png create mode 100644 packages/ep_theme/Resources/Private/Assets/js/_app.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/autocollapse_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/calendar_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/collapse_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/datalayer_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/datepicker_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/daytrip_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/faq_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/form_debounce_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/iframe_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/lightbox_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/modal_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/nav_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/osm_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/overlay_toggle_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/price_calendar_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/pricetable_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/product_details_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_to_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_top_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/search_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_toggle_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/slider_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/sticky_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/tabs_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/teasergrid_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_label_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_toggle_controller.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/main.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/mixins/use_config.js create mode 100644 packages/ep_theme/Resources/Private/Assets/js/mixins/use_fetch.js create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/_base.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/_components.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/_utilities.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/_vendor.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/base/_fonts.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/base/_images.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/base/_rte-content.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/base/_typography.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_background.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_badges.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_buttons.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_calendar.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_contact-bar.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_forms.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_herounit.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_misc.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_searchbar.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_teaser.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_topnav.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/components/_travelinfo.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/main.scss create mode 100644 packages/ep_theme/Resources/Private/Assets/scss/rte.scss create mode 100644 packages/ep_theme/Resources/Private/Forms/AdresscheckForm.form.yaml create mode 100644 packages/ep_theme/Resources/Private/Forms/AdresscheckRemove.form.yaml create mode 100644 packages/ep_theme/Resources/Private/Forms/ApplicationForm.form.yaml create mode 100644 packages/ep_theme/Resources/Private/Forms/ContactForm.form.yaml create mode 100644 packages/ep_theme/Resources/Private/Language/de.locallang.xlf create mode 100644 packages/ep_theme/Resources/Private/Language/locallang.xlf create mode 100644 packages/ep_theme/Resources/Private/Language/locallang_db.xlf create mode 100644 packages/ep_theme/Resources/Private/Language/locallang_mod.xlf create mode 100644 packages/ep_theme/Resources/Private/Layouts/Email/SystemEmail.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Bare.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Default.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Form/Default.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Gridelements/Default.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/News/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/Backend.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/Frontpage.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/FrontpageEvent.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/Popup.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/Search.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/Subpage.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeft.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeftEvent.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRight.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRightEvent.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Page/Travelinfo.html create mode 100644 packages/ep_theme/Resources/Private/Layouts/Product/Default.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Badge.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Chatbot.html create mode 100644 packages/ep_theme/Resources/Private/Partials/City/Details.html create mode 100644 packages/ep_theme/Resources/Private/Partials/City/Teaser.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Concept/Teaser.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Config.html create mode 100644 packages/ep_theme/Resources/Private/Partials/ContactBar.html create mode 100644 packages/ep_theme/Resources/Private/Partials/ContactForm/Error.html create mode 100644 packages/ep_theme/Resources/Private/Partials/ContactForm/Success.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Disrupter/Large.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Disrupter/LastChance.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Disrupter/Outlines.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Disrupter/Reminder.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Disrupter/Small.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Disturber/Large.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Disturber/LastChance.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Disturber/Reminder.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Disturber/Small.html create mode 100644 packages/ep_theme/Resources/Private/Partials/EventDisturber.html create mode 100644 packages/ep_theme/Resources/Private/Partials/EventHero.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Facts.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Faq.html create mode 100644 packages/ep_theme/Resources/Private/Partials/FaqGroup.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Footer.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Captcha.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Checkbox.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/ContentElement.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Date.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Email.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Field/Field.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Fieldset.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Form/Navigation.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/LinkedCheckbox.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/MultiCheckbox.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/MultiSelect.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Number.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/RadioButton.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/SingleSelect.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/StaticText.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Telephone.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Text.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Form/Textarea.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Header/Header.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Header/Standalone.html create mode 100644 packages/ep_theme/Resources/Private/Partials/HeaderSlider.html create mode 100644 packages/ep_theme/Resources/Private/Partials/HeaderSliderSmall.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Hotel/Details.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Hotel/Teaser.html create mode 100644 packages/ep_theme/Resources/Private/Partials/ImageGallery.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Journey/Details.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Mainnav.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Map/Osm.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Mobilenav.html create mode 100644 packages/ep_theme/Resources/Private/Partials/News/Detail/MediaContainer.html create mode 100644 packages/ep_theme/Resources/Private/Partials/News/List/Item.html create mode 100644 packages/ep_theme/Resources/Private/Partials/NotBookable.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Officetip.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Pricetable.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Product/Details.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Product/HeaderDetail.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Product/Pickups.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Product/PriceCalendar.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Product/Teaser.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Rating.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Region/Details.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Region/Teaser.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Region/Webcam.html create mode 100644 packages/ep_theme/Resources/Private/Partials/RegionAward.html create mode 100644 packages/ep_theme/Resources/Private/Partials/RegionMaps.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Regioninfo.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Search/FilterPanel.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Search/Main.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Search/MobileSearch.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Search/Result.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Snowreport.html create mode 100644 packages/ep_theme/Resources/Private/Partials/SubpageHeader.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Teamer.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Teaser/OneColumn.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Teaser/TwoColumn.html create mode 100644 packages/ep_theme/Resources/Private/Partials/TeaserImage.html create mode 100644 packages/ep_theme/Resources/Private/Partials/TeaserWideImage.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Textmedia/Default.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Textmedia/LinkPanel.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Textmedia/Partnerlogos.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Topnav.html create mode 100644 packages/ep_theme/Resources/Private/Partials/Travelinfo.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceCalendar.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceConfig.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxCalendar/PriceTable.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxCalendar/Range.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxFilterPanel/Index.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxSearch/Searchresult.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxTable/Datestable.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxTable/DaytripRooms.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxTable/EventPricetable.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxTable/Pricetable.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxTable/PricetableHtml.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxTeaserPopup/Concept.html create mode 100644 packages/ep_theme/Resources/Private/Templates/AjaxWatchlist/List.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Backend/ListPages.html create mode 100644 packages/ep_theme/Resources/Private/Templates/City/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Templates/City/Teasergroup.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Concept/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Concept/Teasergroup.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Container/Quarters.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Container/Teasergroup.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Country/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Email/Autoreply.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Email/ContactForm.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Email/GroupsPrice.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Faq/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Button.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Calendar.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Contact.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/DealOfTheWeek.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Disrupter.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Disturber.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/EventPricetable.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/FaqOverview.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Gmap.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Header.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Html.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Lineup.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSection.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSectionPages.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/MenuSubpages.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/NewsletterForm.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Shortcut.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Teaser.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserBookingLink.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserCity.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserConcept.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserCountry.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserHotel.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserProduct.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/TeaserRegion.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Text.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Textmedia.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/Textpic.html create mode 100644 packages/ep_theme/Resources/Private/Templates/FluidStyledContent/YoutubeVideo.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Form/Form.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Form/Render.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Gridelements/HalfAndTwoQuarters.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Gridelements/Halves.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Gridelements/Quarters.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Gridelements/Thirds.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Gridelements/TwoQuartersAndHalf.html create mode 100644 packages/ep_theme/Resources/Private/Templates/GroupsPrice/Index.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Hotel/DateIndependentTeasergroup.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Hotel/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Hotel/DetailEvent.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Hotel/Teasergroup.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Map/General.html create mode 100644 packages/ep_theme/Resources/Private/Templates/News/News/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Templates/News/News/List.html create mode 100644 packages/ep_theme/Resources/Private/Templates/News/Tag/List.html create mode 100644 packages/ep_theme/Resources/Private/Templates/News/ViewHelpers/Widget/Paginate/Index.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Newsletter/Index.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Newsletter/SubscriptionForm.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Newsletter/SubscriptionFormSubmit.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/Frontpage.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/FrontpageEvent.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/Popup.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/Search.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/Subpage.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarLeft.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarLeftEvent.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarRight.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/SubpageSidebarRightEvent.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Page/Travelinfo.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Password/Index.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Disrupter.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Disturber.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Gmap.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/Lineup.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserBookingLink.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserCity.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserConcept.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserCountry.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserHotel.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserProduct.html create mode 100644 packages/ep_theme/Resources/Private/Templates/PreviewRenderer/TeaserRegion.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Pricetable/Event.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Product/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Product/HotelList.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Product/Pricetable.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Product/Teasergroup.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Region/Detail.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Region/Teasergroup.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Reseller/Copypaste.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Reseller/List.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Search/Searchresult.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Searchbar/Index.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Snowreport/List.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Team/List.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Team/Show.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelCode.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelInfo.html create mode 100644 packages/ep_theme/Resources/Private/Templates/Watchlist/List.html create mode 100644 packages/ep_theme/composer.json create mode 100644 packages/ep_theme/ext_emconf.php create mode 100644 packages/ep_theme/ext_icon.gif create mode 100644 packages/ep_theme/ext_localconf.php create mode 100644 packages/ep_theme/ext_tables.php create mode 100644 packages/ep_theme/ext_tables.sql diff --git a/composer.json b/composer.json index a12471c..4216ab0 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,8 @@ }, "require": { "b13/container": "^4.1", + "ep/ep-products": "@dev", + "ep/ep-theme": "@dev", "ssch/typo3-encore": "^7.0", "typo3/cms-backend": "^13.4", "typo3/cms-belog": "^13.4", diff --git a/composer.lock b/composer.lock index 5d369cb..3b28889 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d4af0a677424f63a718c41200397902d", + "content-hash": "360f5f33d266a5b828de7280a438b5ce", "packages": [ { "name": "b13/container", @@ -816,6 +816,92 @@ }, "time": "2026-10-20T09:45:10+00:00" }, + { + "name": "ep/ep-products", + "version": "dev-master", + "dist": { + "type": "path", + "url": "./packages/ep_products", + "reference": "291793cfe2a2d5f50bf5bee13f286e501d633a49" + }, + "require": { + "league/oauth2-client": "^2.9", + "league/period": "^4.12", + "php": "^8.2", + "symfony/http-client": "^7.4", + "typo3/cms-core": "^13.4", + "typo3/cms-extbase": "^13.4", + "typo3/cms-fluid": "^13.4", + "typo3/cms-frontend": "^13.4" + }, + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "extension-key": "ep_products" + } + }, + "autoload": { + "psr-4": { + "EP\\EpProducts\\": "Classes/" + } + }, + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Björn Fromme", + "email": "fromme@dreipunktnull.com" + } + ], + "description": "Implementation of all product related logic and models", + "transport-options": { + "relative": true + } + }, + { + "name": "ep/ep-theme", + "version": "dev-master", + "dist": { + "type": "path", + "url": "./packages/ep_theme", + "reference": "f3e425fca030011066b3d2e6a25a756941885b0d" + }, + "require": { + "b13/container": "^4.1", + "ep/ep-products": "@dev", + "php": "^8.2", + "typo3/cms-core": "^13.4", + "typo3/cms-extbase": "^13.4", + "typo3/cms-fluid": "^13.4", + "typo3/cms-fluid-styled-content": "^13.4", + "typo3/cms-frontend": "^13.4" + }, + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "extension-key": "ep_theme" + } + }, + "autoload": { + "psr-4": { + "EP\\EpTheme\\": "Classes/" + } + }, + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Björn Fromme", + "email": "fromme@dreipunktnull.com" + } + ], + "description": "Theme package for Ep Reisen TYPO3 website", + "transport-options": { + "relative": true + } + }, { "name": "firebase/php-jwt", "version": "v7.2.1", @@ -1219,6 +1305,157 @@ ], "time": "2026-10-17T23:57:19+00:00" }, + { + "name": "league/oauth2-client", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "8cedfef9d01a8d1fd2ecfabb41734b17592c4b71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/8cedfef9d01a8d1fd2ecfabb41734b17592c4b71", + "reference": "8cedfef9d01a8d1fd2ecfabb41734b17592c4b71", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.8.2 || ^8.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.6.3 || ^3.0", + "php": "^7.1 || >=8.0.0 <8.6.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.5", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.9.1" + }, + "time": "2026-09-16T13:14:31+00:00" + }, + { + "name": "league/period", + "version": "4.12.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/period.git", + "reference": "766e9dddabbbef7d9655efd857ce977f8541b596" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/period/zipball/766e9dddabbbef7d9655efd857ce977f8541b596", + "reference": "766e9dddabbbef7d9655efd857ce977f8541b596", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.2.0 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.19 || ~v3.4.0", + "phpstan/phpstan": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.0.0", + "phpstan/phpstan-strict-rules": "^1.0.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "League\\Period\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://github.com/nyamsprod/", + "role": "Developer" + } + ], + "description": "Time range API for PHP", + "homepage": "http://period.thephpleague.com", + "keywords": [ + "boundaries", + "calendar", + "collection", + "date", + "dateinterval", + "dateperiod", + "datetime", + "gap", + "intersections", + "interval", + "period", + "range", + "schedule", + "sequence", + "time", + "timeline", + "unions" + ], + "support": { + "docs": "https://period.thephpleague.com", + "issues": "https://github.com/thephpleague/period/issues", + "source": "https://github.com/thephpleague/period" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2022-02-21T08:41:21+00:00" + }, { "name": "lolli42/finediff", "version": "1.1.2", @@ -3420,6 +3657,189 @@ ], "time": "2026-09-26T06:31:10+00:00" }, + { + "name": "symfony/http-client", + "version": "v7.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "121bf3a973bfd852db85ec32d0d64e9fbed4449b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/121bf3a973bfd852db85ec32d0d64e9fbed4449b", + "reference": "121bf3a973bfd852db85ec32d0d64e9fbed4449b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "amphp/socket": "<1.1", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-10-13T15:31:38+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.7.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/35be0019e2c2c9fba80f9dc033290a5240f7b44f", + "reference": "35be0019e2c2c9fba80f9dc033290a5240f7b44f", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-08-04T08:41:16+00:00" + }, { "name": "symfony/http-foundation", "version": "v7.4.21", @@ -9409,7 +9829,10 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "ep/ep-products": 20, + "ep/ep-theme": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": {}, diff --git a/packages/ep_products/Classes/BpnConnect/ApiClient.php b/packages/ep_products/Classes/BpnConnect/ApiClient.php new file mode 100644 index 0000000..68fc1b8 --- /dev/null +++ b/packages/ep_products/Classes/BpnConnect/ApiClient.php @@ -0,0 +1,140 @@ + $hotelCode, + 'dateFrom' => $dateFrom->format('Y-m-d'), + 'mode' => $mode, + ]; + + if (null !== $dateTo) { + $query['dateTo'] = $dateTo->format('Y-m-d'); + } + + $response = $this->request('GET', 'api/v1/contingents/calendar', [ + 'query' => $query, + ]); + + $this->assertStatusCode($response, [Response::HTTP_OK], 'GET', 'api/v1/contingents/calendar'); + + return $this->decodeResponse($response, 'GET', 'api/v1/contingents/calendar'); + } + + private function request(string $method, string $uri, array $options = []): ResponseInterface + { + try { + return $this->getHttpClient()->request($method, $uri, $options); + } catch (TransportExceptionInterface $e) { + throw new ApiException(sprintf('Bpn Connect API transport error for %s %s: %s', $method, $uri, $e->getMessage()), 0, $e); + } + } + + private function assertStatusCode(ResponseInterface $response, array $expectedStatusCodes, string $method, string $uri): void + { + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new ApiException(sprintf('Bpn Connect API transport error while reading status for %s %s: %s', $method, $uri, $e->getMessage()), 0, $e); + } + + if (in_array($statusCode, $expectedStatusCodes, true)) { + return; + } + + throw new ApiException(sprintf('Bpn Connect API returned unexpected status %d for %s %s', $statusCode, $method, $uri), $statusCode); + } + + private function decodeResponse(ResponseInterface $response, string $method, string $uri): array + { + try { + $data = $response->toArray(false); + } catch (DecodingExceptionInterface $e) { + throw new ApiException(sprintf('Bpn Connect API returned invalid JSON for %s %s: %s', $method, $uri, $e->getMessage()), 0, $e); + } catch (TransportExceptionInterface $e) { + throw new ApiException(sprintf('Bpn Connect API transport error while reading body for %s %s: %s', $method, $uri, $e->getMessage()), 0, $e); + } + + if (!isset($data['data']) || !is_array($data['data'])) { + throw new ApiException(sprintf('Bpn Connect API returned a calendar payload without a data array for %s %s', $method, $uri)); + } + + return $data; + } + + private function getHttpClient(): HttpClientInterface + { + $config = $this->getConfiguration(); + $baseUrl = trim((string)($config['bpnConnectApiBaseUrl'] ?? '')); + $apiKey = trim((string)($config['bpnConnectApiKey'] ?? '')); + + if ($baseUrl === '') { + throw new ApiException('Bpn Connect API base URL is not configured.'); + } + if ($apiKey === '') { + throw new ApiException('Bpn Connect API key is not configured.'); + } + + $options = [ + 'headers' => [ + 'X-API-KEY' => $apiKey, + ], + ]; + + // Without these the client falls back to PHP's default_socket_timeout (commonly 60s), + // so a stalled API pins a PHP worker for a minute per request. The defaults live in + // code rather than in ext_conf_template.txt: ExtensionConfiguration::get() returns the + // stored configuration verbatim and only syncs the template when an extension has no + // configuration at all, so a newly added template key is absent until someone saves + // extension configuration by hand. Falling back to 0 there would silently mean "no + // timeout" - the opposite of what this guard is for. An explicit 0 still disables. + $timeout = (int)($config['bpnConnectApiTimeout'] ?? self::DEFAULT_TIMEOUT); + if ($timeout > 0) { + $options['timeout'] = $timeout; + } + $maxDuration = (int)($config['bpnConnectApiMaxDuration'] ?? self::DEFAULT_MAX_DURATION); + if ($maxDuration > 0) { + $options['max_duration'] = $maxDuration; + } + + return HttpClient::createForBaseUri(rtrim($baseUrl, '/') . '/', $options); + } + + private function getConfiguration(): array + { + if (null === $this->configuration) { + $this->configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); + } + + return $this->configuration; + } +} diff --git a/packages/ep_products/Classes/BpnConnect/ApiException.php b/packages/ep_products/Classes/BpnConnect/ApiException.php new file mode 100644 index 0000000..8676702 --- /dev/null +++ b/packages/ep_products/Classes/BpnConnect/ApiException.php @@ -0,0 +1,7 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\ContingentImportService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; + +class ContingentCommand extends Command +{ + + /** + * @var ContingentImportService + */ + protected $contingentImportService; + + /** + * @var string + */ + protected static $defaultName = 'ep:contingent-import'; + + /** + * @param ContingentImportService $contingentImportService + */ + public function __construct() + { + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $this->contingentImportService = $objectManager->get(ContingentImportService::class); + + parent::__construct(); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws \Doctrine\DBAL\DBALException + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $count = $this->contingentImportService->import(); + $output->writeln(sprintf('%d rows imported.', $count )); + + return 0; + } + +} diff --git a/packages/ep_products/Classes/Command/DateCommandController.php b/packages/ep_products/Classes/Command/DateCommandController.php new file mode 100644 index 0000000..a530858 --- /dev/null +++ b/packages/ep_products/Classes/Command/DateCommandController.php @@ -0,0 +1,120 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\DateImportService; +use EP\EpProducts\Service\ProductImportSourceFactory; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; +use TYPO3\CMS\Extbase\Service\CacheService; + +class DateCommandController extends Command +{ + + /** + * @var \EP\EpProducts\Service\DateImportService + */ + protected $dateImportService; + + /** + * @var CacheService + */ + protected $cacheService; + + /** + * @var ConfigurationManagerInterface + */ + protected $configurationManager; + + /** + * @var ProductImportSourceFactory + */ + protected $importSourceFactory; + + /** + * @param DateImportService $importService + * @param CacheService $cacheService + * @param ConfigurationManagerInterface $configurationManager + * @param ProductImportSourceFactory $importSourceFactory + */ + public function __construct + ( + DateImportService $importService, + CacheService $cacheService, + ConfigurationManagerInterface $configurationManager, + ProductImportSourceFactory $importSourceFactory + ) + { + $this->dateImportService = $importService; + $this->cacheService = $cacheService; + $this->configurationManager = $configurationManager; + $this->importSourceFactory = $importSourceFactory; + + parent::__construct(); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws \Doctrine\DBAL\DBALException + */ + public function execute(InputInterface $input, OutputInterface $output): int + { + $importSource = $this->importSourceFactory->get(); + $import = $importSource->acquire(); + if ($import === null) { + $output->writeln('No new import available.'); + return 0; + } + + $count = $this->dateImportService->import($import->getRows()); + $importSource->markImported($import); + $output->writeln(sprintf('%d rows imported.', $count )); + $settings = $this->getSettings(); + $resellerPageUid = $settings['resellerExportPageUid']; + $travelInfoPageUid = $settings['travelInfoPageUid']; + $this->cacheService->clearPageCache([$resellerPageUid, $travelInfoPageUid]); + + return 0; + } + + /** + * @return array + */ + protected function getSettings(): array + { + $settings = $this + ->configurationManager + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); + + return $settings['plugin.']['tx_epproducts.']['settings.']; + } + +} diff --git a/packages/ep_products/Classes/Command/ExportCommandController.php b/packages/ep_products/Classes/Command/ExportCommandController.php new file mode 100644 index 0000000..62c94e6 --- /dev/null +++ b/packages/ep_products/Classes/Command/ExportCommandController.php @@ -0,0 +1,178 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\CsvFormatter\FacebookCatalogCsvFormatter; +use EP\EpProducts\CsvFormatter\GoogleBusinessFeedCsvFormatter; +use EP\EpProducts\Domain\Model\Reseller; +use EP\EpProducts\Domain\Repository\ResellerRepository; +use EP\EpProducts\Service\ResellerDataService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Serializer; +use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; + +class ExportCommandController extends Command +{ + /** + * @var array + */ + protected static $imageDimensions = [ + 'facebook' => [ + 'width' => '1080', + 'maxHeight' => '1080', + 'cropVariant' => 'facebook', + ], + 'google' => [ + 'width' => '1200', + 'maxHeight' => '1200', + 'cropVariant' => 'google', + ] + ]; + + /** + * @var ResellerRepository + */ + protected $resellerRepository; + + /** + * @var ResellerDataService + */ + protected $resellerDataService; + + public function __construct(string $name = null) + { + parent::__construct($name); + + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $this->resellerRepository = $objectManager->get(ResellerRepository::class); + $this->resellerDataService = $objectManager->get(ResellerDataService::class); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws \Doctrine\DBAL\DBALException + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $resellers = $this->resellerRepository->findAll(); + $destinationFolder = sprintf( + '%s/fileadmin/reseller', + \TYPO3\CMS\Core\Core\Environment::getPublicPath() + ); + if (!file_exists($destinationFolder)) { + mkdir($destinationFolder, 0755); + } + foreach ($resellers as $reseller) { + /** @var Reseller $reseller */ + $output->writeln('Exporting XML for ' . $reseller->getName()); + $this->exportXml($reseller, $destinationFolder); + $output->writeln('Exporting CSV (Facebook) for ' . $reseller->getName()); + $this->exportCsv($reseller, 'facebook', $destinationFolder); + $output->writeln('Exporting CSV (Google) for ' . $reseller->getName()); + $this->exportCsv($reseller, 'google', $destinationFolder); + } + + return 0; + } + + /** + * @param Reseller $reseller + * @param string $destinationFolder + * @throws \Doctrine\DBAL\DBALException + */ + protected function exportXml(Reseller $reseller, $destinationFolder) + { + $myEpUrl = $this->getMyEpUrl(); + $data = $this->resellerDataService->preprocessAll($reseller, $myEpUrl); + $encoders = [new XmlEncoder('products')]; + $normalizers = [new ObjectNormalizer()]; + $serializer = new Serializer($normalizers, $encoders); + $xml = $serializer->serialize( + $data, + 'xml', + [ 'xml_encoding' => 'utf-8', 'xml_format_output' => false ] + ); + $filename = sprintf( + '%s/export_%s.xml', + $destinationFolder, + $reseller->getCode() + ); + @unlink($filename); + file_put_contents($filename, $xml); + } + + /** + * @param Reseller $reseller + * @param string $type + * @param string $destinationFolder + * @throws \Doctrine\DBAL\DBALException + */ + protected function exportCsv(Reseller $reseller, $type, $destinationFolder) + { + $myEpUrl = $this->getMyEpUrl(); + $data = $this->resellerDataService->preprocessAll( + $reseller, + $myEpUrl, + static::$imageDimensions[$type], + $type + ); + if ($type === 'google') { + $csv = GoogleBusinessFeedCsvFormatter::format($data); + } else { + $csv = FacebookCatalogCsvFormatter::format($data); + } + $filename = sprintf( + '%s/export_%s_%s.csv', + $destinationFolder, + $reseller->getCode(), + $type + ); + @unlink($filename); + $fileHandle = fopen($filename, 'wb'); + foreach ($csv as $row) { + fputcsv($fileHandle, $row); + } + fclose($fileHandle); + } + + protected function getMyEpUrl(): string + { + $config = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); + + return $config['myEpUrl'] ?? 'https://my.ep-reisen.de'; + } +} diff --git a/packages/ep_products/Classes/Command/GroupsSwissCommand.php b/packages/ep_products/Classes/Command/GroupsSwissCommand.php new file mode 100644 index 0000000..cbe9888 --- /dev/null +++ b/packages/ep_products/Classes/Command/GroupsSwissCommand.php @@ -0,0 +1,85 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\GroupsSwissService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; + +class GroupsSwissCommand extends Command +{ + /** + * @var GroupsSwissService + */ + private $groupsSwissService; + + protected static $defaultName = 'ep:groups-swiss'; + + public function __construct() + { + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $this->groupsSwissService = $objectManager->get(GroupsSwissService::class); + + parent::__construct(); + } + + protected function configure() + { + $this->addArgument( + 'hotel', + InputArgument::REQUIRED, + 'E&P hotel uid' + ); + $this->addArgument( + 'house', + InputArgument::REQUIRED, + 'Groups.swiss house number' + ); + $this->addArgument( + 'key', + InputArgument::REQUIRED, + 'Groups.swiss API key' + ); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $hotelUid = $input->getArgument('hotel'); + $houseNumber = $input->getArgument('house'); + $apiKey = $input->getArgument('key'); + + $success = $this->groupsSwissService->publishContingents($hotelUid, $houseNumber, $apiKey); + + return $success ? 0 : 1; + } +} diff --git a/packages/ep_products/Classes/Command/SnowreportCommandController.php b/packages/ep_products/Classes/Command/SnowreportCommandController.php new file mode 100644 index 0000000..0efe92b --- /dev/null +++ b/packages/ep_products/Classes/Command/SnowreportCommandController.php @@ -0,0 +1,62 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\SnowreportImportService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class SnowreportCommandController extends Command +{ + + /** + * @var SnowreportImportService + */ + protected $snowreportImportService; + + /** + * @param SnowreportImportService $snowreportImportService + */ + public function __construct(SnowreportImportService $snowreportImportService) + { + $this->snowreportImportService = $snowreportImportService; + + parent::__construct(); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $this->snowreportImportService->downloadReportsXml(); + $count = $this->snowreportImportService->importSnowReportData(); + $output->writeln(sprintf('%d rows imported.', $count )); + + return 0; + } + +} diff --git a/packages/ep_products/Classes/ContentFeed/ContentFeedApi.php b/packages/ep_products/Classes/ContentFeed/ContentFeedApi.php new file mode 100644 index 0000000..230fd82 --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/ContentFeedApi.php @@ -0,0 +1,165 @@ +authenticator = $authenticator; + $this->configuration = $configuration; + $this->manifestBuilder = $manifestBuilder; + $this->recordSerializer = $recordSerializer; + $this->cache = $cacheManager->getCache('ep_products_cache'); + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + if (!$this->authenticator->isAuthorized($request)) { + return $this->error('Invalid or missing api key', 403); + } + + if (!$this->configuration->isEnabled()) { + return self::disabledResponse(); + } + + if ([] === $this->configuration->getSites()) { + // An empty feed would read as "everything was deleted". Refuse instead. + return $this->error('Content feed has no sites configured', 503); + } + + $path = substr($request->getUri()->getPath(), strlen(self::PATH_PREFIX)); + + if ('manifest' === $path) { + return new JsonResponse($this->getManifest(), 200, [], self::JSON_FLAGS); + } + + if ('records' === $path) { + return $this->handleRecordsRequest($request); + } + + return $this->error('Not found', 404); + } + + /** + * Uids that are not (or no longer) eligible are left out rather than answered with an error: + * the manifest may have changed between the consumer's two calls, and it reads a requested + * but absent uid as deleted. + */ + private function handleRecordsRequest(ServerRequestInterface $request): ResponseInterface + { + $type = (string) ($request->getQueryParams()['type'] ?? ''); + + if (!isset(RecordFinder::TYPES[$type])) { + return $this->error('Unknown type, expected one of: ' . implode(', ', array_keys(RecordFinder::TYPES)), 400); + } + + $uids = array_values(array_unique(array_filter( + GeneralUtility::intExplode(',', (string) ($request->getQueryParams()['uids'] ?? ''), true), + function (int $uid): bool { + return $uid > 0; + } + ))); + + if ([] === $uids) { + return $this->error('No uids given', 400); + } + + if (count($uids) > self::MAX_RECORDS_PER_REQUEST) { + return $this->error(sprintf('At most %d uids per request', self::MAX_RECORDS_PER_REQUEST), 400); + } + + return new JsonResponse([ + 'type' => $type, + 'items' => $this->recordSerializer->serialize($type, $uids), + ], 200, [], self::JSON_FLAGS); + } + + private function getManifest(): array + { + $manifest = $this->cache->get(self::MANIFEST_CACHE_IDENTIFIER); + + if (!is_array($manifest)) { + $manifest = $this->manifestBuilder->build(); + $this->cache->set(self::MANIFEST_CACHE_IDENTIFIER, $manifest, [], self::MANIFEST_CACHE_LIFETIME); + } + + return $manifest; + } + + /** + * The kill switch: contentFeedEnabled = 0 in the extension configuration, for when the host + * is struggling. The consumer stops its run, keeps everything it has and tries again later - + * nothing is deleted on the chatbot side. + */ + public static function disabledResponse(): ResponseInterface + { + return new JsonResponse( + ['message' => 'Content feed is switched off'], + 503, + ['Retry-After' => (string) self::DISABLED_RETRY_AFTER], + self::JSON_FLAGS + ); + } + + private function error(string $message, int $status): ResponseInterface + { + return new JsonResponse(['message' => $message], $status, [], self::JSON_FLAGS); + } +} diff --git a/packages/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php b/packages/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php new file mode 100644 index 0000000..342485a --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/ContentFeedConfiguration.php @@ -0,0 +1,75 @@ +configuration = (array) $extensionConfiguration->get('ep_products'); + } + + public function isEnabled(): bool + { + return (bool) ($this->configuration['contentFeedEnabled'] ?? true); + } + + /** + * @return string[] site identifiers + */ + public function getSites(): array + { + return GeneralUtility::trimExplode(',', (string) ($this->configuration['contentFeedSites'] ?? ''), true); + } + + /** + * @return int[] language ids; the default language when unset + */ + public function getLanguages(): array + { + $languages = GeneralUtility::intExplode(',', (string) ($this->configuration['contentFeedLanguages'] ?? '0'), true); + + return [] === $languages ? [0] : $languages; + } + + /** + * @return int[] page uids whose whole subtree stays out of the feed + */ + public function getExcludedPages(): array + { + return GeneralUtility::intExplode(',', (string) ($this->configuration['contentFeedExcludedPages'] ?? ''), true); + } + + /** + * @return array site identifier => news detail page uid + */ + public function getNewsDetailPages(): array + { + $pages = []; + + foreach (GeneralUtility::trimExplode(',', (string) ($this->configuration['contentFeedNewsDetailPages'] ?? ''), true) as $entry) { + $parts = GeneralUtility::trimExplode(':', $entry, true); + + if (2 === count($parts) && (int) $parts[1] > 0) { + $pages[$parts[0]] = (int) $parts[1]; + } + } + + return $pages; + } +} diff --git a/packages/ep_products/Classes/ContentFeed/ManifestBuilder.php b/packages/ep_products/Classes/ContentFeed/ManifestBuilder.php new file mode 100644 index 0000000..32fe25c --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/ManifestBuilder.php @@ -0,0 +1,379 @@ + ['uid', 'tstamp', 'detail_page'], + 'hotel' => ['uid', 'tstamp', 'detail_page'], + 'region' => ['uid', 'tstamp', 'detail_page'], + 'country' => ['uid', 'tstamp', 'detail_page'], + 'city' => ['uid', 'tstamp', 'detail_page'], + 'concept' => ['uid', 'tstamp', 'detail_page'], + 'news' => ['uid', 'tstamp', 'pid'], + ]; + + /** + * @var PageTree + */ + private $pageTree; + + /** + * @var UrlResolver + */ + private $urlResolver; + + /** + * @var RecordFinder + */ + private $recordFinder; + + /** + * @var ContentFeedConfiguration + */ + private $configuration; + + /** + * @var ConnectionPool + */ + private $connectionPool; + + public function __construct( + PageTree $pageTree, + UrlResolver $urlResolver, + RecordFinder $recordFinder, + ContentFeedConfiguration $configuration, + ConnectionPool $connectionPool + ) { + $this->pageTree = $pageTree; + $this->urlResolver = $urlResolver; + $this->recordFinder = $recordFinder; + $this->configuration = $configuration; + $this->connectionPool = $connectionPool; + } + + public function build(): array + { + $sites = $this->urlResolver->getIncludedSites(); + $records = $this->buildRecordItems(); + + return [ + 'generatedAt' => date('c'), + 'sites' => array_map([$this, 'describeSite'], $sites), + 'items' => array_merge($this->buildPageItems($sites, $this->collectDetailPages()), $records), + ]; + } + + private function describeSite(Site $site): array + { + $languages = []; + + foreach ($site->getLanguages() as $language) { + $languages[] = $language->getTwoLetterIsoCode(); + } + + $title = trim((string) ($site->getConfiguration()['websiteTitle'] ?? '')); + + if ('' === $title) { + $title = trim($site->getDefaultLanguage()->getWebsiteTitle()); + } + + return [ + 'identifier' => $site->getIdentifier(), + 'title' => '' !== $title ? $title : $site->getIdentifier(), + 'base' => (string) $site->getBase(), + 'languages' => $languages, + ]; + } + + /** + * @param Site[] $sites + * @param array $detailPages detailOf entries by page uid + */ + private function buildPageItems(array $sites, array $detailPages): array + { + $content = $this->findContentStatistics(); + $excluded = array_flip($this->configuration->getExcludedPages()); + $feedLanguages = $this->configuration->getLanguages(); + $items = []; + + foreach ($sites as $site) { + $languageIds = []; + + foreach ($site->getLanguages() as $language) { + if (in_array($language->getLanguageId(), $feedLanguages, true)) { + $languageIds[$language->getLanguageId()] = $language->getTwoLetterIsoCode(); + } + } + + $context = [ + 'site' => $site, + 'languages' => $languageIds, + 'excluded' => $excluded, + 'content' => $content, + 'detailPages' => $detailPages, + ]; + $breadcrumbs = array_fill_keys(array_keys($languageIds), []); + + $this->walk($site->getRootPageId(), $context, $breadcrumbs, true, $items); + } + + return $items; + } + + /** + * @param array $breadcrumbs titles above this page, by language id + */ + private function walk(int $uid, array $context, array $breadcrumbs, bool $isRoot, array &$items): void + { + $page = $this->pageTree->getPage($uid); + + if (null === $page || isset($context['excluded'][$uid])) { + return; + } + + if (in_array((int) $page['doktype'], self::DOKTYPES_NOT_TRAVERSED, true)) { + return; + } + + $visible = $this->pageTree->isVisible($page); + + if (!$visible && (int) $page['extendToSubpages'] === 1) { + return; + } + + $childBreadcrumbs = $breadcrumbs; + + foreach ($context['languages'] as $languageId => $isoCode) { + $row = $this->getLanguageRow($page, $languageId); + + if ($visible && null !== $row && $this->isPublishable($page, $row, $languageId)) { + $item = $this->buildPageItem($page, $row, $languageId, $isoCode, $breadcrumbs[$languageId], $context); + + if (null !== $item) { + $items[] = $item; + } + } + + if (!$isRoot) { + $source = $row ?? $page; + $childBreadcrumbs[$languageId][] = '' !== trim((string) $source['nav_title']) + ? trim((string) $source['nav_title']) + : trim((string) $source['title']); + } + } + + foreach ($this->pageTree->getChildren($uid) as $childUid) { + $this->walk($childUid, $context, $childBreadcrumbs, false, $items); + } + } + + private function getLanguageRow(array $page, int $languageId): ?array + { + if (0 === $languageId) { + return $page; + } + + $overlay = $this->pageTree->getOverlay((int) $page['uid'], $languageId); + + return null !== $overlay && $this->pageTree->isVisible($overlay) ? $overlay : null; + } + + /** + * Mirrors the XML sitemap: standard pages only, nothing marked no_index. If Google is not + * meant to see a page, the chatbot is not either. + */ + private function isPublishable(array $page, array $row, int $languageId): bool + { + if ((int) $page['doktype'] !== self::DOKTYPE_DEFAULT || (int) $row['no_index'] === 1) { + return false; + } + + return 0 !== $languageId || 0 === ((int) $page['l18n_cfg'] & self::L18N_HIDE_DEFAULT_LANGUAGE); + } + + private function buildPageItem(array $page, array $row, int $languageId, string $isoCode, array $breadcrumb, array $context): ?array + { + $uid = (int) $page['uid']; + // "Show content from page": the elements live on another page, and so do their changes. + // The site answers 404 when that page is hidden. + $contentPid = (int) $page['content_from_pid'] ?: $uid; + $contentPage = $this->pageTree->getPage($contentPid); + + if (null === $contentPage || !$this->pageTree->isVisible($contentPage)) { + return null; + } + + // Without a visible element other than plugins, the content-only render is empty (it + // leaves plugins out), so there is nothing to fetch. That is most product and hotel + // detail pages: their text comes through the records endpoint. + $content = $context['content'][$contentPid] ?? []; + $textElements = ($content[$languageId]['text'] ?? 0) + ($content[-1]['text'] ?? 0); + + if (0 === $textElements) { + return null; + } + + $url = $this->urlResolver->getPageUrl($uid, $languageId); + + if (null === $url) { + return null; + } + + $modified = max( + (int) $row['tstamp'], + $content[$languageId]['tstamp'] ?? 0, + $content[-1]['tstamp'] ?? 0 + ); + + return [ + 'type' => 'page', + 'uid' => $uid, + 'language' => $isoCode, + 'site' => $context['site']->getIdentifier(), + 'url' => $url, + 'contentUrl' => $this->urlResolver->getPageUrl($uid, $languageId, ['type' => self::CONTENT_PAGE_TYPE]), + 'title' => trim((string) $row['title']), + 'description' => $this->nullIfEmpty((string) $row['description']), + 'breadcrumb' => $breadcrumb, + 'detailOf' => $context['detailPages'][$uid] ?? [], + 'modified' => date('c', $modified), + ]; + } + + /** + * Per page and language: the latest change to any content element, and how many visible + * elements are not plugins. + * Deleted and hidden elements count towards the change - removing or hiding one changes the + * page - which is also why SYS_LASTCHANGED is no use here: it only moves on render. + * + * @return array> + */ + private function findContentStatistics(): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content'); + $queryBuilder->getRestrictions()->removeAll(); + + $rows = $queryBuilder + ->selectLiteral( + 'pid', + 'sys_language_uid', + 'MAX(tstamp) AS tstamp', + "SUM(CASE WHEN deleted = 0 AND hidden = 0 AND CType <> 'list' THEN 1 ELSE 0 END) AS text_elements" + ) + ->from('tt_content') + ->groupBy('pid', 'sys_language_uid') + ->execute() + ->fetchAllAssociative() + ; + + $statistics = []; + + foreach ($rows as $row) { + $statistics[(int) $row['pid']][(int) $row['sys_language_uid']] = [ + 'tstamp' => (int) $row['tstamp'], + 'text' => (int) $row['text_elements'], + ]; + } + + return $statistics; + } + + /** + * Which records name a page as their detail page. Keyed by the page the record points at, + * not by where a shortcut leads: a shortcut is not in the manifest, and the page it leads to + * usually describes something broader (product detail shortcuts point at region pages). + * + * @return array + */ + private function collectDetailPages(): array + { + $detailPages = []; + + foreach (self::RECORD_FIELDS as $type => $fields) { + if (!in_array('detail_page', $fields, true)) { + continue; + } + + foreach ($this->recordFinder->find($type, ['uid', 'detail_page']) as $uid => $row) { + $pageUid = $this->urlResolver->parsePageReference((string) $row['detail_page']); + + if (null !== $pageUid) { + $detailPages[$pageUid][] = ['type' => $type, 'uid' => $uid]; + } + } + } + + return $detailPages; + } + + private function buildRecordItems(): array + { + $faqTimestamps = $this->recordFinder->findFaqTimestamps(); + $matchcodeTimestamps = $this->recordFinder->findMatchcodeTimestamps(); + $items = []; + + foreach (self::RECORD_FIELDS as $type => $fields) { + foreach ($this->recordFinder->find($type, $fields) as $uid => $row) { + if ('news' === $type) { + $link = $this->urlResolver->getNewsLink((int) $row['uid'], (int) $row['pid']); + + // News has no detail_page of its own: an article stored outside the feed's + // sites (the retired ep-events) is not part of the feed at all. + if (null === $link) { + continue; + } + } else { + $link = $this->urlResolver->getRecordLink((string) $row['detail_page']); + } + + $modified = (int) $row['tstamp']; + + if ('product' === $type) { + $modified = max($modified, $faqTimestamps[$uid] ?? 0); + } + + if ('hotel' === $type) { + $modified = max($modified, $matchcodeTimestamps[$uid] ?? 0); + } + + $items[] = [ + 'type' => $type, + 'uid' => $uid, + 'language' => 'de', + 'site' => $link['site'], + 'url' => $link['url'], + 'modified' => date('c', $modified), + ]; + } + } + + return $items; + } + + private function nullIfEmpty(string $value): ?string + { + $value = trim($value); + + return '' === $value ? null : $value; + } +} diff --git a/packages/ep_products/Classes/ContentFeed/PageTree.php b/packages/ep_products/Classes/ContentFeed/PageTree.php new file mode 100644 index 0000000..b2d1b0c --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/PageTree.php @@ -0,0 +1,131 @@ +|null default-language rows by uid + */ + private $pages; + + /** + * @var array child uids by parent uid, in sorting order + */ + private $children = []; + + /** + * @var array> overlay rows by default-language uid and language id + */ + private $overlays = []; + + public function __construct(ConnectionPool $connectionPool, Context $context) + { + $this->connectionPool = $connectionPool; + $this->now = (int) $context->getPropertyFromAspect('date', 'timestamp'); + } + + public function getPage(int $uid): ?array + { + $this->load(); + + return $this->pages[$uid] ?? null; + } + + public function getOverlay(int $uid, int $languageId): ?array + { + $this->load(); + + return $this->overlays[$uid][$languageId] ?? null; + } + + /** + * @return int[] + */ + public function getChildren(int $uid): array + { + $this->load(); + + return $this->children[$uid] ?? []; + } + + /** + * Visible to an anonymous frontend visitor: not hidden, inside its start/end window, and not + * restricted to a frontend user group. + */ + public function isVisible(array $row): bool + { + if ((int) $row['hidden'] === 1) { + return false; + } + + if ((int) $row['starttime'] > $this->now) { + return false; + } + + if ((int) $row['endtime'] !== 0 && (int) $row['endtime'] <= $this->now) { + return false; + } + + return in_array((string) $row['fe_group'], ['', '0'], true); + } + + private function load(): void + { + if (null !== $this->pages) { + return; + } + + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions()->removeAll()->add(new DeletedRestriction()); + + $rows = $queryBuilder + ->select(...self::FIELDS) + ->from('pages') + ->orderBy('sorting') + ->execute() + ->fetchAllAssociative() + ; + + $this->pages = []; + + foreach ($rows as $row) { + $uid = (int) $row['uid']; + + if ((int) $row['sys_language_uid'] > 0) { + $this->overlays[(int) $row['l10n_parent']][(int) $row['sys_language_uid']] = $row; + continue; + } + + $this->pages[$uid] = $row; + $this->children[(int) $row['pid']][] = $uid; + } + } +} diff --git a/packages/ep_products/Classes/ContentFeed/RecordFinder.php b/packages/ep_products/Classes/ContentFeed/RecordFinder.php new file mode 100644 index 0000000..a69c1f8 --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/RecordFinder.php @@ -0,0 +1,152 @@ + 'tx_epproducts_domain_model_product', + 'hotel' => 'tx_epproducts_domain_model_hotel', + 'region' => 'tx_epproducts_domain_model_region', + 'country' => 'tx_epproducts_domain_model_country', + 'city' => 'tx_epproducts_domain_model_city', + 'concept' => 'tx_epproducts_domain_model_concept', + 'news' => 'tx_news_domain_model_news', + ]; + + /** + * @var ConnectionPool + */ + private $connectionPool; + + public function __construct(ConnectionPool $connectionPool) + { + $this->connectionPool = $connectionPool; + } + + /** + * @param string[] $fields + * @param int[]|null $uids null for all eligible records + * @return array rows by uid, in uid order + */ + public function find(string $type, array $fields, ?array $uids = null): array + { + $table = self::TYPES[$type]; + $queryBuilder = $this->connectionPool->getQueryBuilderForTable($table); + $queryBuilder + ->select(...$fields) + ->from($table) + ->where($queryBuilder->expr()->in('sys_language_uid', [0, -1])) + ->orderBy('uid') + ; + + if (null !== $uids) { + if ([] === $uids) { + return []; + } + + $queryBuilder->andWhere($queryBuilder->expr()->in( + 'uid', + $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY) + )); + } + + $this->addTypeConstraints($type, $queryBuilder); + + $rows = []; + + foreach ($queryBuilder->execute()->fetchAllAssociative() as $row) { + $rows[(int) $row['uid']] = $row; + } + + return $rows; + } + + /** + * Latest change to a product's FAQs, by product uid. Deleted and hidden FAQs count: removing + * one is a change to the product's text. + * + * @return array + */ + public function findFaqTimestamps(): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_faq'); + $queryBuilder->getRestrictions()->removeAll(); + + $rows = $queryBuilder + ->selectLiteral('mm.uid_local AS product', 'MAX(faq.tstamp) AS tstamp') + ->from('tx_epproducts_product_faq_mm', 'mm') + ->join('mm', 'tx_epproducts_domain_model_faq', 'faq', 'faq.uid = mm.uid_foreign') + ->groupBy('mm.uid_local') + ->execute() + ->fetchAllAssociative() + ; + + return $this->indexTimestamps($rows, 'product'); + } + + /** + * Latest change to a hotel's matchcode aliases, by hotel uid. They are part of the hotel's + * `codes`, which the consumer joins its trip data on. + * + * @return array + */ + public function findMatchcodeTimestamps(): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_matchcode'); + $queryBuilder->getRestrictions()->removeAll(); + + $rows = $queryBuilder + ->selectLiteral('entity AS hotel', 'MAX(tstamp) AS tstamp') + ->from('tx_epproducts_domain_model_matchcode') + ->where($queryBuilder->expr()->eq('entity_type', $queryBuilder->createNamedParameter('hotel'))) + ->groupBy('entity') + ->execute() + ->fetchAllAssociative() + ; + + return $this->indexTimestamps($rows, 'hotel'); + } + + /** + * @return array + */ + private function indexTimestamps(array $rows, string $key): array + { + $timestamps = []; + + foreach ($rows as $row) { + $timestamps[(int) $row[$key]] = (int) $row['tstamp']; + } + + return $timestamps; + } + + private function addTypeConstraints(string $type, QueryBuilder $queryBuilder): void + { + $expr = $queryBuilder->expr(); + + if ('product' === $type) { + // A product that is neither searchable nor bookable is an internal record. + $queryBuilder->andWhere($expr->orX($expr->eq('searchable', 1), $expr->eq('bookable', 1))); + } + + if ('news' === $type) { + // Type 0 is a regular article; 1 and 2 are just links to a page or an external URL. + $queryBuilder->andWhere( + $expr->eq('type', $queryBuilder->createNamedParameter('0')), + $expr->in('fe_group', $queryBuilder->createNamedParameter(['', '0'], Connection::PARAM_STR_ARRAY)) + ); + } + } +} diff --git a/packages/ep_products/Classes/ContentFeed/RecordSerializer.php b/packages/ep_products/Classes/ContentFeed/RecordSerializer.php new file mode 100644 index 0000000..2ed22cc --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/RecordSerializer.php @@ -0,0 +1,513 @@ +recordFinder = $recordFinder; + $this->urlResolver = $urlResolver; + $this->rteLinkResolver = $rteLinkResolver; + $this->connectionPool = $connectionPool; + } + + /** + * @param int[] $uids + * @return array[] one item per eligible uid; ineligible uids are left out + */ + public function serialize(string $type, array $uids): array + { + $rows = $this->recordFinder->find($type, ['*'], $uids); + + if ([] === $rows) { + return []; + } + + switch ($type) { + case 'product': + return $this->serializeProducts($rows); + case 'hotel': + return $this->serializeHotels($rows); + case 'news': + return $this->serializeNews($rows); + default: + return array_values(array_map(function (array $row) use ($type): array { + return $this->serializeDestination($type, $row); + }, $rows)); + } + } + + private function serializeProducts(array $rows): array + { + $uids = array_keys($rows); + $hotels = $this->findMmUids('tx_epproducts_product_hotel_mm', 'uid_local', 'uid_foreign', $uids); + $regions = $this->findMmUids('tx_epproducts_product_region_mm', 'uid_local', 'uid_foreign', $uids); + $faqs = $this->findFaqs($uids); + $faqTimestamps = $this->recordFinder->findFaqTimestamps(); + $infoboxes = $this->findInfoboxTexts(array_column($rows, 'non_bookable_info')); + $items = []; + + foreach ($rows as $uid => $row) { + $season = trim((string) $row['season']); + + $items[] = $this->withLink($row, max((int) $row['tstamp'], $faqTimestamps[$uid] ?? 0)) + [ + 'code' => $this->text($row['code']), + 'busProId' => (int) $row['bus_pro_id'] ?: null, + 'name' => $this->text($row['name']), + 'subline' => $this->text($row['subline']), + 'headline' => $this->text($row['headline']), + 'season' => '' === $season ? 'w' : $season, + 'daytrip' => (bool) $row['daytrip'], + 'bookable' => (bool) $row['bookable'], + 'searchable' => (bool) $row['searchable'], + 'skipassIncluded' => (bool) $row['skipass_included'], + 'conceptUid' => (int) $row['concept'] ?: null, + 'countryUid' => (int) $row['country'] ?: null, + 'regionUid' => (int) $row['region'] ?: null, + 'cityUid' => (int) $row['city'] ?: null, + 'additionalRegionUids' => $regions[$uid] ?? [], + 'hotelUids' => $hotels[$uid] ?? [], + 'keywords' => $this->text($row['keywords']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'teaserLong' => $this->html($row['teaser_long']), + 'feature' => $this->text($row['feature']), + 'conceptDescription' => $this->html($row['concept_description']), + 'boardDescription' => $this->html($row['board_description']), + 'includedServices' => $this->html($row['included_services']), + 'additionalServices' => $this->html($row['additional_services']), + 'programmeDescription' => $this->html($row['programme_description']), + 'skiPassDescription' => $this->html($row['ski_pass_description']), + 'nonBookableInfo' => $infoboxes[(int) $row['non_bookable_info']] ?? null, + ], + 'faqs' => $faqs[$uid] ?? [], + ]; + } + + return $items; + } + + private function serializeHotels(array $rows): array + { + $uids = array_keys($rows); + $aliases = $this->findMatchcodes($uids); + $matchcodeTimestamps = $this->recordFinder->findMatchcodeTimestamps(); + $products = $this->findMmUids('tx_epproducts_product_hotel_mm', 'uid_foreign', 'uid_local', $uids, 'sorting_foreign'); + $eligibleProducts = $this->recordFinder->find('product', ['uid'], array_values(array_unique(array_merge([], ...array_values($products))))); + $items = []; + + foreach ($rows as $uid => $row) { + $code = trim((string) $row['code']); + $codes = array_values(array_unique(array_filter(array_merge([$code], $aliases[$uid] ?? []), 'strlen'))); + + $items[] = $this->withLink($row, max((int) $row['tstamp'], $matchcodeTimestamps[$uid] ?? 0)) + [ + 'code' => '' === $code ? null : $code, + 'codes' => $codes, + 'name' => $this->text($row['name']), + 'shortName' => $this->text($row['short_name']), + 'headline' => $this->text($row['headline']), + 'type' => (int) $row['type'], + 'category' => (int) $row['category'], + 'new' => (bool) $row['new'], + 'earlybird' => (bool) $row['earlybird'], + 'countryUid' => (int) $row['country'] ?: null, + 'regionUid' => (int) $row['region'] ?: null, + 'cityUid' => (int) $row['city'] ?: null, + 'productUids' => array_values(array_filter($products[$uid] ?? [], function (int $productUid) use ($eligibleProducts): bool { + return isset($eligibleProducts[$productUid]); + })), + 'address' => $this->text($row['address']), + 'latitude' => $this->text($row['latitude']), + 'longitude' => $this->text($row['longitude']), + 'facts' => [ + 'bedsCount' => (int) $row['beds_count'] ?: null, + 'parkingCount' => (int) $row['parking_count'] ?: null, + 'altitude' => $this->text($row['altitude']), + 'distanceBus' => $this->text($row['distance_bus']), + 'distanceLift' => $this->text($row['distance_lift']), + 'shower' => $this->text($row['shower']), + 'partyroom' => (bool) $row['partyroom'], + 'gamesroom' => (bool) $row['gamesroom'], + 'sauna' => (bool) $row['sauna'], + 'wifi' => (bool) $row['wifi'], + ], + 'icons' => HotelIconUtility::getIcons($row), + 'keywords' => $this->text($row['keywords']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'features' => $this->html($row['features']), + 'roomTypes' => $this->html($row['room_types']), + 'additionalInformation' => $this->html($row['additional_information']), + ], + ]; + } + + return $items; + } + + private function serializeDestination(string $type, array $row): array + { + $item = $this->withLink($row, (int) $row['tstamp']) + [ + 'name' => $this->text($row['name']), + 'headline' => $this->text($row['headline']), + ]; + + switch ($type) { + case 'region': + return $item + [ + 'season' => $this->text($row['season']), + 'keywords' => $this->text($row['keywords']), + 'countryUid' => (int) $row['country'] ?: null, + 'facts' => [ + 'length' => $this->text($row['length']), + 'height' => $this->text($row['height']), + 'lifts' => $this->text($row['lifts']), + ], + 'webcam' => $this->findWebcamUrl((int) $row['webcam']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'feature' => $this->text($row['feature']), + 'skiArea' => $this->html($row['ski_area']), + 'skiAreaExtended' => $this->html($row['ski_area_extended']), + 'skiPass' => $this->html($row['ski_pass']), + 'news' => $this->html($row['news']), + ], + ]; + case 'country': + return $item + [ + 'code' => $this->text($row['code']), + 'season' => $this->text($row['season']), + 'keywords' => $this->text($row['keywords']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'descriptionExtended' => $this->html($row['description_extended']), + ], + ]; + case 'city': + return $item + [ + 'season' => $this->text($row['season']), + 'keywords' => $this->text($row['keywords']), + 'countryUid' => (int) $row['country'] ?: null, + 'regionUid' => (int) $row['region'] ?: null, + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'descriptionExtended' => $this->html($row['description_extended']), + 'party' => $this->html($row['party']), + 'leisure' => $this->html($row['leisure']), + ], + ]; + default: // concept + return $item + [ + 'code' => $this->text($row['code']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'descriptionExtended' => $this->html($row['description_extended']), + 'popupText' => $this->html($row['popup_text']), + ], + ]; + } + } + + private function serializeNews(array $rows): array + { + $uids = array_keys($rows); + $tags = $this->findNewsTags($uids); + $categories = $this->findNewsCategories($uids); + $items = []; + + foreach ($rows as $uid => $row) { + $link = $this->urlResolver->getNewsLink($uid, (int) $row['pid']); + + if (null === $link) { + continue; + } + + $items[] = [ + 'uid' => $uid, + 'url' => $link['url'], + 'site' => $link['site'], + 'modified' => date('c', (int) $row['tstamp']), + 'title' => $this->text($row['title']), + 'teaser' => $this->html($row['teaser']), + 'bodytext' => $this->html($row['bodytext']), + 'datetime' => (int) $row['datetime'] > 0 ? date('c', (int) $row['datetime']) : null, + 'tags' => $tags[$uid] ?? [], + 'categories' => $categories[$uid] ?? [], + ]; + } + + return $items; + } + + /** + * The identity and link fields every record carries, with the same values as the manifest. + */ + private function withLink(array $row, int $modified): array + { + $link = $this->urlResolver->getRecordLink((string) $row['detail_page']); + + return [ + 'uid' => (int) $row['uid'], + 'url' => $link['url'], + 'site' => $link['site'], + 'modified' => date('c', $modified), + ]; + } + + /** + * @param int[] $uids + * @return array related uids by $keyField, in $sortField order + */ + private function findMmUids(string $table, string $keyField, string $valueField, array $uids, string $sortField = 'sorting'): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable($table); + $rows = $queryBuilder + ->select($keyField, $valueField) + ->from($table) + ->where($queryBuilder->expr()->in($keyField, $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY))) + ->orderBy($keyField) + ->addOrderBy($sortField) + ->execute() + ->fetchAllAssociative() + ; + + $related = []; + + foreach ($rows as $row) { + $related[(int) $row[$keyField]][] = (int) $row[$valueField]; + } + + return $related; + } + + /** + * @param int[] $productUids + * @return array FAQs by product uid, in the order the editor arranged them + */ + private function findFaqs(array $productUids): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_faq'); + $rows = $queryBuilder + ->select('mm.uid_local AS product', 'faq.question', 'faq.answer', 'faq.category') + ->from('tx_epproducts_domain_model_faq', 'faq') + ->join('faq', 'tx_epproducts_product_faq_mm', 'mm', 'mm.uid_foreign = faq.uid') + ->where($queryBuilder->expr()->in('mm.uid_local', $queryBuilder->createNamedParameter($productUids, Connection::PARAM_INT_ARRAY))) + ->orderBy('mm.uid_local') + ->addOrderBy('mm.sorting') + ->execute() + ->fetchAllAssociative() + ; + + $faqs = []; + + foreach ($rows as $row) { + $faqs[(int) $row['product']][] = [ + 'question' => $this->text($row['question']), + 'answer' => $this->html($row['answer']), + 'category' => $this->text($row['category']), + ]; + } + + return $faqs; + } + + /** + * Matchcode aliases by hotel uid. With the hotel's own code they are every code BusPro may use + * for it - the same set DateImportService::getHotelMappings() resolves. + * + * @param int[] $hotelUids + * @return array + */ + private function findMatchcodes(array $hotelUids): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_matchcode'); + $rows = $queryBuilder + ->select('entity', 'code') + ->from('tx_epproducts_domain_model_matchcode') + ->where( + $queryBuilder->expr()->eq('entity_type', $queryBuilder->createNamedParameter('hotel')), + $queryBuilder->expr()->in('entity', $queryBuilder->createNamedParameter($hotelUids, Connection::PARAM_INT_ARRAY)) + ) + ->orderBy('code') + ->execute() + ->fetchAllAssociative() + ; + + $codes = []; + + foreach ($rows as $row) { + $codes[(int) $row['entity']][] = trim((string) $row['code']); + } + + return $codes; + } + + /** + * @param array $infoboxUids + * @return array + */ + private function findInfoboxTexts(array $infoboxUids): array + { + $infoboxUids = array_values(array_filter(array_map('intval', $infoboxUids))); + + if ([] === $infoboxUids) { + return []; + } + + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_infobox'); + $rows = $queryBuilder + ->select('uid', 'text') + ->from('tx_epproducts_domain_model_infobox') + ->where($queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter(array_unique($infoboxUids), Connection::PARAM_INT_ARRAY))) + ->execute() + ->fetchAllAssociative() + ; + + $texts = []; + + foreach ($rows as $row) { + $texts[(int) $row['uid']] = $this->html($row['text']); + } + + return $texts; + } + + private function findWebcamUrl(int $webcamUid): ?string + { + if (0 === $webcamUid) { + return null; + } + + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_webcam'); + $url = $queryBuilder + ->select('url') + ->from('tx_epproducts_domain_model_webcam') + ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($webcamUid, \PDO::PARAM_INT))) + ->execute() + ->fetchOne() + ; + + return false === $url ? null : $this->text($url); + } + + /** + * @param int[] $newsUids + * @return array + */ + private function findNewsTags(array $newsUids): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_news_domain_model_tag'); + $rows = $queryBuilder + ->select('mm.uid_local AS news', 'tag.title') + ->from('tx_news_domain_model_tag', 'tag') + ->join('tag', 'tx_news_domain_model_news_tag_mm', 'mm', 'mm.uid_foreign = tag.uid') + ->where($queryBuilder->expr()->in('mm.uid_local', $queryBuilder->createNamedParameter($newsUids, Connection::PARAM_INT_ARRAY))) + ->orderBy('mm.sorting') + ->execute() + ->fetchAllAssociative() + ; + + $tags = []; + + foreach ($rows as $row) { + $tags[(int) $row['news']][] = trim((string) $row['title']); + } + + return $tags; + } + + /** + * @param int[] $newsUids + * @return array + */ + private function findNewsCategories(array $newsUids): array + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_category'); + $rows = $queryBuilder + ->select('mm.uid_foreign AS news', 'category.title') + ->from('sys_category', 'category') + ->join('category', 'sys_category_record_mm', 'mm', 'mm.uid_local = category.uid') + ->where( + $queryBuilder->expr()->eq('mm.tablenames', $queryBuilder->createNamedParameter('tx_news_domain_model_news')), + $queryBuilder->expr()->eq('mm.fieldname', $queryBuilder->createNamedParameter('categories')), + $queryBuilder->expr()->in('mm.uid_foreign', $queryBuilder->createNamedParameter($newsUids, Connection::PARAM_INT_ARRAY)) + ) + ->orderBy('mm.sorting_foreign') + ->execute() + ->fetchAllAssociative() + ; + + $categories = []; + + foreach ($rows as $row) { + $categories[(int) $row['news']][] = trim((string) $row['title']); + } + + return $categories; + } + + /** + * Rich text as stored, with internal links made absolute. + */ + private function html($value): ?string + { + $value = trim((string) $value); + + return '' === $value ? null : $this->rteLinkResolver->resolve($value); + } + + /** + * Plain text with whitespace collapsed. Product names carry literal line breaks the website + * splits on for layout ("Apartments \nVal Thorens"); the chatbot needs them as one line. + */ + private function text($value): ?string + { + $value = trim((string) preg_replace('/\s+/u', ' ', (string) $value)); + + return '' === $value ? null : $value; + } +} diff --git a/packages/ep_products/Classes/ContentFeed/RteLinkResolver.php b/packages/ep_products/Classes/ContentFeed/RteLinkResolver.php new file mode 100644 index 0000000..83dafd8 --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/RteLinkResolver.php @@ -0,0 +1,104 @@ +linkService = $linkService; + $this->urlResolver = $urlResolver; + } + + public function resolve(string $html): string + { + if (false === strpos($html, 't3://')) { + return $html; + } + + return (string) preg_replace_callback( + '/\shref=(["\'])(t3:\/\/[^"\']*)\1/i', + function (array $matches): string { + $url = $this->resolveLink(html_entity_decode($matches[2], ENT_QUOTES | ENT_HTML5)); + + return null === $url ? '' : ' href="' . htmlspecialchars($url) . '"'; + }, + $html + ); + } + + private function resolveLink(string $link): ?string + { + try { + $data = $this->linkService->resolve($link); + } catch (\Throwable $e) { + return null; + } + + if (LinkService::TYPE_PAGE === ($data['type'] ?? null)) { + $pageUid = $this->urlResolver->parsePageReference('t3://page?uid=' . ($data['pageuid'] ?? '')); + $target = null === $pageUid ? null : $this->urlResolver->resolveLinkTarget($pageUid); + $url = null === $target ? null : $this->urlResolver->getPageUrl($target); + + if (null !== $url && '' !== (string) ($data['fragment'] ?? '')) { + $url .= '#' . $data['fragment']; + } + + return $url; + } + + if (LinkService::TYPE_FILE === ($data['type'] ?? null) && ($data['file'] ?? null) instanceof File) { + return $this->absolute((string) $data['file']->getPublicUrl()); + } + + if (LinkService::TYPE_URL === ($data['type'] ?? null)) { + return (string) $data['url']; + } + + return null; + } + + /** + * File URLs come back relative to the document root; the consumer has no page to resolve + * them against for record text, so make them absolute against the main site. + */ + private function absolute(string $url): ?string + { + if ('' === $url) { + return null; + } + + if (1 === preg_match('/^https?:\/\//', $url)) { + return $url; + } + + $sites = $this->urlResolver->getIncludedSites(); + + if ([] === $sites) { + return null; + } + + return rtrim((string) $sites[0]->getBase(), '/') . '/' . ltrim($url, '/'); + } +} diff --git a/packages/ep_products/Classes/ContentFeed/UrlResolver.php b/packages/ep_products/Classes/ContentFeed/UrlResolver.php new file mode 100644 index 0000000..a52c644 --- /dev/null +++ b/packages/ep_products/Classes/ContentFeed/UrlResolver.php @@ -0,0 +1,214 @@ + news detail page uid by site identifier + */ + private $newsDetailPages; + + /** + * @var array + */ + private $sitesByPage = []; + + public function __construct(SiteFinder $siteFinder, PageTree $pageTree, ContentFeedConfiguration $configuration) + { + $this->siteFinder = $siteFinder; + $this->pageTree = $pageTree; + $this->includedSites = $configuration->getSites(); + $this->newsDetailPages = $configuration->getNewsDetailPages(); + } + + /** + * @return Site[] included sites, in configuration order + */ + public function getIncludedSites(): array + { + $sites = []; + + foreach ($this->includedSites as $identifier) { + try { + $sites[] = $this->siteFinder->getSiteByIdentifier($identifier); + } catch (SiteNotFoundException $e) { + // A misspelt identifier must not take the whole feed down; it just contributes nothing. + } + } + + return $sites; + } + + /** + * The page's site, or null when it belongs to no site or to one outside the feed. + */ + public function getIncludedSite(int $pageUid): ?Site + { + if (!array_key_exists($pageUid, $this->sitesByPage)) { + try { + $site = $this->siteFinder->getSiteByPageId($pageUid); + $this->sitesByPage[$pageUid] = in_array($site->getIdentifier(), $this->includedSites, true) ? $site : null; + } catch (SiteNotFoundException $e) { + $this->sitesByPage[$pageUid] = null; + } + } + + return $this->sitesByPage[$pageUid]; + } + + public function getPageUrl(int $pageUid, int $languageId = 0, array $parameters = []): ?string + { + $site = $this->getIncludedSite($pageUid); + + if (null === $site) { + return null; + } + + try { + $language = $site->getLanguageById($languageId); + + return (string) $site->getRouter()->generateUri($pageUid, ['_language' => $language] + $parameters); + } catch (\Throwable $e) { + // Unroutable pages (missing slug, disabled language) are left out rather than failing the feed. + return null; + } + } + + /** + * Parses a typolink page reference as stored in `detail_page`: a plain uid or `t3://page?uid=N`. + */ + public function parsePageReference(string $reference): ?int + { + $reference = trim($reference); + + if (MathUtility::canBeInterpretedAsInteger($reference)) { + return (int) $reference > 0 ? (int) $reference : null; + } + + if (1 === preg_match('/^t3:\/\/page\?(?:.*&)?uid=(\d+)/', html_entity_decode($reference), $matches)) { + return (int) $matches[1]; + } + + return null; + } + + /** + * Follows shortcut pages the way a typolink does, and returns the uid of the visible standard + * page a link to $pageUid ends up on - or null when the chain breaks or loops. + */ + public function resolveLinkTarget(int $pageUid): ?int + { + for ($depth = 0; $depth < self::MAX_SHORTCUT_DEPTH; $depth++) { + $page = $this->pageTree->getPage($pageUid); + + if (null === $page || !$this->pageTree->isVisible($page)) { + return null; + } + + if ((int) $page['doktype'] !== self::DOKTYPE_SHORTCUT) { + return (int) $page['doktype'] === self::DOKTYPE_DEFAULT ? $pageUid : null; + } + + $next = $this->getShortcutTarget($page); + + if (null === $next) { + return null; + } + + $pageUid = $next; + } + + return null; + } + + /** + * @return array{url: ?string, site: ?string} + */ + public function getRecordLink(string $detailPage): array + { + $pageUid = $this->parsePageReference($detailPage); + $target = null === $pageUid ? null : $this->resolveLinkTarget($pageUid); + $site = null === $target ? null : $this->getIncludedSite($target); + $url = null === $site ? null : $this->getPageUrl($target); + + return [ + 'url' => $url, + 'site' => null === $url ? null : $site->getIdentifier(), + ]; + } + + /** + * News has no detail_page of its own: the storage folder decides the site, and the site's + * configured detail page carries the article through the NewsDetailPlugin route enhancer. + * + * @return array{url: ?string, site: string}|null null when the article is stored outside the feed's sites + */ + public function getNewsLink(int $newsUid, int $storagePid): ?array + { + $site = $this->getIncludedSite($storagePid); + + if (null === $site) { + return null; + } + + $detailPage = $this->newsDetailPages[$site->getIdentifier()] ?? null; + $url = null === $detailPage ? null : $this->getPageUrl($detailPage, 0, [ + 'tx_news_pi1' => ['action' => 'detail', 'controller' => 'News', 'news' => $newsUid], + ]); + + return ['url' => $url, 'site' => $site->getIdentifier()]; + } + + private function getShortcutTarget(array $page): ?int + { + switch ((int) $page['shortcut_mode']) { + case self::SHORTCUT_MODE_NONE: + return (int) $page['shortcut'] > 0 ? (int) $page['shortcut'] : null; + case self::SHORTCUT_MODE_FIRST_SUBPAGE: + foreach ($this->pageTree->getChildren((int) $page['uid']) as $childUid) { + if ($this->pageTree->isVisible($this->pageTree->getPage($childUid))) { + return $childUid; + } + } + + return null; + case self::SHORTCUT_MODE_PARENT_PAGE: + return (int) $page['pid'] > 0 ? (int) $page['pid'] : null; + default: + // Random subpage: there is no stable URL to give. + return null; + } + } +} diff --git a/packages/ep_products/Classes/Controller/AjaxCalendarController.php b/packages/ep_products/Classes/Controller/AjaxCalendarController.php new file mode 100644 index 0000000..259b283 --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxCalendarController.php @@ -0,0 +1,391 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\BpnConnect\ApiClient as BpnConnectClient; +use EP\EpProducts\MyEP\ApiClient as MyEpClient; +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\MyEP\ApiException; +use EP\EpProducts\Utility\DateUtility; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; + +class AjaxCalendarController extends ActionController +{ + private const CACHE_TTL = 3600; + + protected BpnConnectClient $bpnConnectClient; + protected MyEpClient $myEpClient; + protected FrontendInterface $cache; + + public function __construct(BpnConnectClient $bpnConnectClient, MyEpClient $myEpClient, FrontendInterface $cache) + { + $this->bpnConnectClient = $bpnConnectClient; + $this->myEpClient = $myEpClient; + $this->cache = $cache; + } + + public function priceCalendarAction(Hotel $hotel, int $months = 12): void + { + $hotelCode = trim($hotel->getCode() ?? ''); + $this->tagPageCacheForHotel($hotelCode); + + $dateRange = DateUtility::getDateRange(null, null, $months); + $period = $dateRange['period']; + $today = new \DateTimeImmutable('today'); + + $priceDataByDate = []; + $error = false; + $errorMessage = ''; + + if ($hotelCode !== '') { + try { + // This action is cacheable, so the rendered markup is held in the page cache + // for config.cache_period. An additional application-level cache here would + // key on exactly the same granularity (hotel + date range) and only stack a + // second TTL on top, pushing worst-case staleness past the freshness budget. + $rawData = $this->myEpClient->getPriceConfigData($hotelCode); + + $availableDates = []; + foreach ($rawData as $item) { + if (($item['status'] ?? '') === 'OK') { + $availableDates[$item['date']] = true; + } + } + + // The API no longer honours a date-range constraint, so it may return data + // further out than the default $months grid covers. Grow the grid to match + // whatever the API actually returned rather than silently dropping the tail. + $lastDataDate = null; + + foreach ($rawData as $item) { + $date = $item['date']; + $dt = \DateTimeImmutable::createFromFormat('Y-m-d', $date); + if ($dt < $today) { + continue; + } + + if (null === $lastDataDate || $dt > $lastDataDate) { + $lastDataDate = $dt; + } + + $prevDate = $dt->modify('-1 day')->format('Y-m-d'); + $isAvailable = ($item['status'] ?? '') === 'OK'; + $prevAvailable = isset($availableDates[$prevDate]); + + if ($isAvailable) { + $dayStatus = $prevAvailable ? 'ok' : 'blocked-to-ok'; + } elseif ($prevAvailable) { + $dayStatus = 'checkout-only'; + } else { + $dayStatus = 'blocked'; + } + + $priceDataByDate[$date] = [ + 'status' => $dayStatus, + 'available' => $isAvailable ? 1 : 0, + 'tooltip' => $this->buildTooltip($dayStatus, $item), + 'pricePerNight' => $item['pricePerNight'] ?? null, + 'defaultPricePerNight' => $item['defaultPricePerNight'] ?? null, + 'type' => $item['type'] ?? null, + 'currency' => $item['currency'] ?? null, + 'includedPax' => $item['includedPax'] ?? null, + 'minNights' => (int)($item['minNights'] ?? 0), + ]; + } + + if (null !== $lastDataDate) { + $dataEnd = $lastDataDate->modify('first day of next month')->setTime(0, 0, 0); + if ($dataEnd > $period->getEndDate()) { + $period = new \League\Period\Period($period->getStartDate(), $dataEnd); + } + } + } catch (ApiException $e) { + $error = true; + $errorMessage = $e->getMessage(); + $this->disablePageCache(); + } + } else { + $error = true; + $errorMessage = 'Missing hotel code'; + $this->disablePageCache(); + } + + $this->view->assignMultiple([ + 'priceDataByDate' => $priceDataByDate, + 'calendarMonths' => $this->buildCalendarMonths($period), + 'error' => $error, + 'errorMessage' => $errorMessage, + ]); + } + + public function priceConfigAction(Hotel $hotel): void + { + $hotelCode = trim($hotel->getCode() ?? ''); + $this->tagPageCacheForHotel($hotelCode); + + $priceConfig = [ + 'data' => [], + 'success' => true, + 'error' => null, + ]; + + try { + $priceConfig['data'] = $this->myEpClient->getPriceConfigData($hotelCode); + } catch (ApiException $e) { + $priceConfig['success'] = false; + $priceConfig['error'] = $e->getMessage(); + $this->disablePageCache(); + } + + $this->view->assign('priceConfig', $priceConfig); + } + + public function priceTableAction(Hotel $hotel, string $year = null): void + { + $year = $year ?? (new \DateTimeImmutable())->format('Y'); + $hotelCode = trim($hotel->getCode() ?? ''); + $this->tagPageCacheForHotel($hotelCode); + + $priceTable = [ + 'data' => [], + 'success' => true, + 'error' => null, + ]; + + try { + $priceTable['data'] = $this->myEpClient->getPriceTableData($hotelCode, $year); + } catch (ApiException $e) { + $priceTable['success'] = false; + $priceTable['error'] = $e->getMessage(); + $this->disablePageCache(); + } + + $this->view->assignMultiple([ + 'hotel' => $hotel, + 'year' => $year, + 'priceTable' => $priceTable, + ]); + } + + /** + * @param Hotel $hotel + * @param string $year + * @param string $month + * @param int $months + */ + public function rangeAction(Hotel $hotel, $year = null, $month = null, $months = 1) + { + $dateRange = DateUtility::getDateRange($year, $month, $months); + $period = $dateRange['period']; + $apiDateFrom = $dateRange['dateFrom']; + $apiDateTo = $dateRange['dateTo']; + $hotelCode = trim((string) $hotel->getCode()); + + $calendar = [ + 'meta' => [], + 'data' => [], + ]; + + if ($hotelCode === '') { + $calendar['error'] = true; + } else { + try { + $cacheIdentifier = $this->getCacheIdentifier( + $hotelCode, + $apiDateFrom, + $apiDateTo, + BpnConnectClient::CALENDAR_MODE_DAYS + ); + $cachedCalendar = $this->cache->get($cacheIdentifier); + + if (false !== $cachedCalendar) { + $calendar = $cachedCalendar; + } else { + $calendar = $this->bpnConnectClient->getCalendar( + $hotelCode, + new \DateTimeImmutable($apiDateFrom), + new \DateTimeImmutable($apiDateTo), + BpnConnectClient::CALENDAR_MODE_DAYS + ); + $this->cache->set($cacheIdentifier, $calendar, [], self::CACHE_TTL); + } + } catch (\Throwable $e) { + $calendar['error'] = true; + } + } + + $this->view->assignMultiple([ + 'months' => $months, + 'hotel' => $hotel, + 'calendar' => $calendar, + 'calendarMonths' => $this->buildCalendarMonths($period), + ]); + } + + /** + * Tag this page-cache entry with the hotel it renders, so the entry can be flushed for + * that hotel alone rather than by clearing the whole page cache and re-warming every + * calendar. Tags are stored alongside the entry by + * TypoScriptFrontendController::setPageCacheContent(). + * + * Used manually today, when one hotel's availability needs re-fetching before its hour + * is up: + * + * typo3cms cache:flushtags ep_hotel_ --groups pages + */ + private function tagPageCacheForHotel(string $hotelCode): void + { + if ('' === $hotelCode) { + return; + } + + if (isset($GLOBALS['TSFE']) && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController) { + $GLOBALS['TSFE']->addCacheTags(['ep_hotel_' . $hotelCode]); + } + } + + /** + * The calendar actions are cacheable, so an API failure would otherwise pin the error + * markup in the page cache for the full cache_period. Drop caching for this request so + * the next visitor gets a fresh attempt instead of an hour-old error message. + * + * The second argument must be true: set_no_cache() ignores the call otherwise, because + * $TYPO3_CONF_VARS['FE']['disableNoCacheParameter'] defaults to true. + */ + private function disablePageCache(): void + { + if (isset($GLOBALS['TSFE']) && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController) { + $GLOBALS['TSFE']->set_no_cache('Price calendar data could not be retrieved', true); + } + } + + private function buildTooltip(string $status, array $item): string + { + if ($status === 'blocked') { + return 'Nicht verfügbar'; + } + if ($status === 'checkout-only') { + return 'Nur Abreise'; + } + return $this->buildPriceTooltip($item); + } + + private function buildPriceTooltip(array $item): string + { + $price = $item['pricePerNight'] ?? null; + $currency = $this->getCurrencySymbol($item['currency'] ?? null); + + if ($price !== null) { + $priceStr = (($item['type'] ?? null) === 'discount') + ? $price . ' ' . $currency + : 'ab ' . $price . ' ' . $currency; + } else { + $priceStr = 'Anfrage'; + } + + $minNights = (int)($item['minNights'] ?? 0); + + return $minNights > 1 + ? $priceStr . ' · min. ' . $minNights . ' Nächte' + : $priceStr; + } + + private function getCurrencySymbol(?string $currency): string + { + if (null === $currency || '' === $currency) { + return '€'; + } + + return 'EUR' === $currency ? '€' : $currency; + } + + /** + * @return array> + */ + private function buildCalendarMonths(\League\Period\Period $period): array + { + $months = []; + $current = new \DateTimeImmutable($period->getStartDate()->format('Y-m-01 00:00:00')); + $lastDay = $period->getEndDate()->sub(new \DateInterval('P1D')); + $lastMonthStart = new \DateTimeImmutable($lastDay->format('Y-m-01 00:00:00')); + + while ($current <= $lastMonthStart) { + $months[] = $this->buildCalendarMonth($current); + $current = $current->modify('first day of next month'); + } + + return $months; + } + + /** + * @return array + */ + private function buildCalendarMonth(\DateTimeImmutable $monthStart): array + { + $firstOfMonth = $monthStart->setTime(0, 0, 0); + $lastOfMonth = $firstOfMonth->modify('last day of this month'); + $gridStart = $firstOfMonth->modify('monday this week'); + $gridEnd = $lastOfMonth->modify('sunday this week'); + + $weeks = []; + $week = []; + $current = $gridStart; + + while ($current <= $gridEnd) { + $week[] = [ + 'date' => $current, + 'inMonth' => $current->format('Y-m') === $firstOfMonth->format('Y-m'), + ]; + + if (count($week) === 7) { + $weeks[] = $week; + $week = []; + } + + $current = $current->add(new \DateInterval('P1D')); + } + + return [ + 'date' => $firstOfMonth, + 'weeks' => $weeks, + ]; + } + + private function getCacheIdentifier(string $hotelCode, string $dateFrom, string $dateTo, string $mode): string + { + return 'bpn_connect_calendar_' . sha1(implode('|', [ + $hotelCode, + $dateFrom, + $dateTo, + $mode, + ])); + } + +} diff --git a/packages/ep_products/Classes/Controller/AjaxContingentController.php b/packages/ep_products/Classes/Controller/AjaxContingentController.php new file mode 100644 index 0000000..265ed1d --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxContingentController.php @@ -0,0 +1,102 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\ContingentRepository; +use EP\EpProducts\Service\ContingentDataService; +use EP\EpProducts\Service\MyEpUrlResolverService; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class AjaxContingentController extends ActionController +{ + /** + * @var \EP\EpProducts\Domain\Repository\ContingentRepository + */ + protected $contingentRepository; + /** + * @var ContingentDataService + */ + protected $contingentDataService; + + /** + * @var MyEpUrlResolverService + */ + protected $myEpUrlResolverService; + + /** + * @param ContingentRepository $contingentRepository + * @param ContingentDataService $contingentDataService + * @param MyEpUrlResolverService $myEpUrlResolverService + */ + public function __construct( + ContingentRepository $contingentRepository, + ContingentDataService $contingentDataService, + MyEpUrlResolverService $myEpUrlResolverService + ) { + $this->contingentRepository = $contingentRepository; + $this->contingentDataService = $contingentDataService; + $this->myEpUrlResolverService = $myEpUrlResolverService; + } + + public function initializeAction() + { + if ($this->request->hasArgument('dateFrom')) { + $dateFrom = new \DateTime($this->request->getArgument('dateFrom')); + $this->request->setArgument('dateFrom', $dateFrom); + } + if ($this->request->hasArgument('dateTo')) { + $dateTo = new \DateTime($this->request->getArgument('dateTo')); + $this->request->setArgument('dateTo', $dateTo); + } + } + + /** + * @param Hotel $hotel + * @param Product $product + * @param \DateTime $dateFrom + * @param \DateTime $dateTo + * @return string + * @throws \Doctrine\DBAL\DBALException + */ + public function roomsAction(Hotel $hotel, Product $product, \DateTime $dateFrom, \DateTime $dateTo) + { + $myEpUrl = $this->myEpUrlResolverService->resolve(); + $rooms = $this->contingentDataService->getAvailableRooms( + $dateFrom->format('Y-m-d'), + $dateTo->format('Y-m-d'), + $hotel, + $product, + $myEpUrl + ); + + return \json_encode($rooms); + } + +} diff --git a/packages/ep_products/Classes/Controller/AjaxDateController.php b/packages/ep_products/Classes/Controller/AjaxDateController.php new file mode 100644 index 0000000..54f215a --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxDateController.php @@ -0,0 +1,148 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Service\DateService; +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\ResellerDataService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +class AjaxDateController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var DateService + */ + protected $dateService; + + /** + * @var ResellerDataService + */ + protected $resellerDataService; + + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @param FilterService $filterService + * @param DateService $dateService + * @param ResellerDataService $resellerDataService + * @param ProductRepository $productRepository + */ + public function __construct + ( + FilterService $filterService, + DateService $dateService, + ResellerDataService $resellerDataService, + ProductRepository $productRepository + ) + { + $this->filterService = $filterService; + $this->dateService = $dateService; + $this->resellerDataService = $resellerDataService; + $this->productRepository = $productRepository; + } + + public function initializeDatesAction() + { + if ($this->request->hasArgument('dateFrom')) { + $dateFrom = $this->request->getArgument('dateFrom'); + $this->request->setArgument('dateFrom', $dateFrom === '' ? null : $dateFrom); + } + if ($this->request->hasArgument('dateTo')) { + $dateTo = $this->request->getArgument('dateTo'); + $this->request->setArgument('dateTo', $dateTo === '' ? null : $dateTo); + } + } + + /** + * @param Product $product + * @param Hotel $hotel + * @param string $paCode + * @param string $dateFrom + * @param string $dateTo + * + * @return string + * @throws \Doctrine\DBAL\DBALException + */ + public function datesAction(Product $product, Hotel $hotel = null, $paCode = null, $dateFrom = null, $dateTo = null) + { + $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); + $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids); + $filterSettings['dateFrom'] = $dateFrom; + $filterSettings['dateTo'] = $dateTo; + + $altProductLink = null; + $altLabel = null; + if ($product->getAltProduct()) { + $altProduct = $product->getAltProduct(); + if ($this->productRepository->hasDates($altProduct)) { + /** @var ContentObjectRenderer $contentObject */ + $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class); + $altProductLink = $contentObject->typoLink_URL( + [ + 'parameter' => $altProduct->getDetailPage(), + ] + ); + $altLabel = $product->getAltLabel(); + } + } + $datesTable = $this->dateService->getDatesTable([ + 'product' => $product, + 'hotel' => $hotel, + 'filterSettings' => $filterSettings, + ]); + $hasSurchargeOrDiscount = $this->dateService->checkSurcharcheOrDiscount($datesTable); + + return json_encode([ + 'dates' => $datesTable, + 'bookable' => $product->getBookable(), + 'hotelName' => $hotel->getName(), + 'altProductLink' => $altProductLink, + 'altLabel' => $altLabel, + 'hasSurcharge' => $hasSurchargeOrDiscount['surcharge'], + 'hasDiscount' => $hasSurchargeOrDiscount['discount'], + ], JSON_THROW_ON_ERROR); + } + +} diff --git a/packages/ep_products/Classes/Controller/AjaxFilterPanelController.php b/packages/ep_products/Classes/Controller/AjaxFilterPanelController.php new file mode 100644 index 0000000..840b8bb --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxFilterPanelController.php @@ -0,0 +1,100 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\SearchResultService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class AjaxFilterPanelController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var SearchResultService + */ + private $searchResultService; + + /** + * @param FilterService $filterService + */ + public function __construct(FilterService $filterService, SearchResultService $searchResultService) + { + $this->filterService = $filterService; + $this->searchResultService = $searchResultService; + } + + public function initializeAction() + { + $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); + $this->processFilterSettingsArgument($forcedConceptUids); + } + + /** + * @param array $filterSettings + */ + public function indexAction(array $filterSettings) + { + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']); + + $searchResult = $this + ->searchResultService + ->getResult($filterSettings, $excludedConceptUids); + + $filterOptions = $this->filterService->collectFilterOptionsFromSearchResult($searchResult); + + $resultsUri = $this->uriBuilder + ->reset() + ->setCreateAbsoluteUri(true) + ->setTargetPageUid((int) $this->settings['defaultSearchPageUid']) + ->uriFor( + 'searchresult', + ['filterSettings' => $filterSettings], + 'Search', + 'epproducts', + 'searchresult' + ); + + $filterOptions->setResultsUri($resultsUri); + + $this->view->assignMultiple([ + 'filterSettings' => $filterSettings, + 'filterOptions' => $filterOptions, + 'stimulusController' => 'mobile-search', + ]); + } + +} diff --git a/packages/ep_products/Classes/Controller/AjaxSearchController.php b/packages/ep_products/Classes/Controller/AjaxSearchController.php new file mode 100644 index 0000000..9a2ec05 --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxSearchController.php @@ -0,0 +1,132 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\SearchResultService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use EP\EpProducts\Utility\DateUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class AjaxSearchController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var SearchResultService + */ + protected $searchResultService; + + /** + * @param FilterService $filterService + * @param SearchResultService $searchResultService + */ + public function __construct(FilterService $filterService, SearchResultService $searchResultService) + { + $this->filterService = $filterService; + $this->searchResultService = $searchResultService; + } + + public function initializeSearchresultAction() + { + $forcedConceptUids = GeneralUtility::intExplode(',', $this->settings['forcedConceptUids'], true); + $this->processFilterSettingsArgument($forcedConceptUids); + } + + /** + * @param array $filterSettings + */ + public function searchresultAction(array $filterSettings): void + { + $dateSelected = false; + $organic = true; + $forcedConceptUids = GeneralUtility::intExplode(',', $this->settings['forcedConceptUids'], true); + $excludedConceptUids = GeneralUtility::intExplode(',', $this->settings['excludedConceptUids'], true); + + if (isset($filterSettings['dateFrom']) || isset($filterSettings['dateTo'])) { + $dateSelected = true; + } + + $searchResult = $this + ->searchResultService + ->getResult($filterSettings, $excludedConceptUids); + + // No results? First retry without date constraint if any + if ((!empty($filterSettings['dateFrom']) || !empty($filterSettings['dateTo'])) && 0 === $searchResult->getTotal()) { + $filterSettings['dateFrom'] = null; + $filterSettings['dateTo'] = null; + $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); + $organic = false; + } + + // Still no results? Remove all constraints and search again with defaults + if (0 === $searchResult->getTotal()) { + $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids); + $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); + $organic = false; + } + + $filterOptions = $this->filterService->collectFilterOptionsFromSearchResult($searchResult); + + $referringPageUri = $this + ->uriBuilder + ->reset() + ->setTargetPageUid((int)$this->settings['defaultSearchPageUid']) + ->setCreateAbsoluteUri(true) + ->uriFor( + 'searchresult', + ['filterSettings' => $filterSettings], + 'Search', + 'epproducts', + 'searchresult' + ) + ; + + $this->view->assignMultiple([ + 'filterSettings' => $filterSettings, + 'filterOptions' => $filterOptions, + 'searchResult' => $searchResult->getData(), + 'referringPageUri' => $referringPageUri, + 'stimulusController' => 'search', + 'meta' => [ + 'organic' => $organic, + 'total' => $searchResult->getTotal(), + 'dateRange' => DateUtility::formatDateRange($filterSettings['dateFrom'], $filterSettings['dateTo']), + 'dateSelected' => $dateSelected, + ], + ]); + } + +} diff --git a/packages/ep_products/Classes/Controller/AjaxTableController.php b/packages/ep_products/Classes/Controller/AjaxTableController.php new file mode 100644 index 0000000..8b3cdc1 --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxTableController.php @@ -0,0 +1,386 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Date; +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\InfoboxRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Service\ContingentDataService; +use EP\EpProducts\Service\DateService; +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\MyEpUrlResolverService; +use EP\EpProducts\Service\ResellerDataService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use League\Period\Period; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +class AjaxTableController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @var InfoboxRepository + */ + protected $infoboxRepository; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var DateService + */ + protected $dateService; + + /** + * @var ResellerDataService + */ + protected $resellerDataService; + + /** + * @var ContingentDataService + */ + protected $contingentDataService; + + /** + * @var MyEpUrlResolverService + */ + protected $myEpUrlResolverService; + + /** + * @param ProductRepository $productRepository + * @param InfoboxRepository $infoboxRepository + * @param DateService $dateService + * @param FilterService $filterService + * @param ResellerDataService $resellerDataService + * @param ContingentDataService $contingentDataService + * @param MyEpUrlResolverService $myEpUrlResolverService + */ + public function __construct( + ProductRepository $productRepository, + InfoboxRepository $infoboxRepository, + DateService $dateService, + FilterService $filterService, + ResellerDataService $resellerDataService, + ContingentDataService $contingentDataService, + MyEpUrlResolverService $myEpUrlResolverService + ) { + $this->productRepository = $productRepository; + $this->infoboxRepository = $infoboxRepository; + $this->dateService = $dateService; + $this->filterService = $filterService; + $this->resellerDataService = $resellerDataService; + $this->contingentDataService = $contingentDataService; + $this->myEpUrlResolverService = $myEpUrlResolverService; + } + + public function initializeDatesAction() + { + if ($this->request->hasArgument('dateFrom')) { + $dateFrom = $this->request->getArgument('dateFrom'); + $this->request->setArgument('dateFrom', $dateFrom === '' ? null : $dateFrom); + } + if ($this->request->hasArgument('dateTo')) { + $dateTo = $this->request->getArgument('dateTo'); + $this->request->setArgument('dateTo', $dateTo === '' ? null : $dateTo); + } + } + + /** + * @param Product $product + * @param Hotel $hotel + * @param string|null $dateFrom + * @param string|null $dateTo + */ + public function datestableAction( + Product $product, + Hotel $hotel, + string $dateFrom = null, + string $dateTo = null + ): void { + $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); + $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids); + $filterSettings['dateFrom'] = $dateFrom; + $filterSettings['dateTo'] = $dateTo; + $paCode = GeneralUtility::_GET('pa'); + $myEpUrl = $this->myEpUrlResolverService->resolve(); + + $dates = $this->dateService->getDatesTable([ + 'product' => $product, + 'hotel' => $hotel, + 'filterSettings' => $filterSettings, + ]); + + if (1 === count($dates)) { + $date = reset($dates); + $this->forward( + 'pricetable', + null, + 'ep_products', + [ + 'product' => $product, + 'hotel' => $hotel, + 'date' => $date['dateUid'], + 'paCode' => $paCode, + 'singleDate' => true, + ] + ); + } + + $nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true); + + $allDates = $this->dateService->preprocessDatesTable([ + 'dateTableData' => $dates, + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + 'showBookingButton' => false === $product->getHideBookingButton(), + 'nonBookableDateUids' => $nonBookableDateUids, + ]); + + $bookableDates = array_filter($allDates, function (array $date) { + return $date['available'] > 0; + }); + $includedServices = $this->dateService->groupIncludedServices($bookableDates); + + $altProductLink = null; + $altLabel = null; + if ($product->getAltProduct()) { + $altProduct = $product->getAltProduct(); + if ($this->productRepository->hasDates($altProduct)) { + /** @var ContentObjectRenderer $contentObject */ + $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class); + $altProductLink = $contentObject->typoLink_URL( + [ + 'parameter' => $altProduct->getDetailPage(), + ] + ); + $altLabel = $product->getAltLabel(); + } + } + + $hasSurchargeOrDiscount = $this->dateService->checkSurcharcheOrDiscount($allDates); +// $hasIncludedServices = $this->dateService->hasIncludedServices($allDates); + $hasIncludedServices = true; + $noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids']); + $isProductWithNoInfoConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(), + $noInfoConceptUids, false); + + $nonBookableInfo = $this->getNonBookableInfobox($product); + + $this->view->assignMultiple([ + 'allDates' => $allDates, + 'bookableDates' => $bookableDates, + 'includedServicesAllDates' => $includedServices['allDates'], + 'includedServicesSomeDates' => $includedServices['someDates'], + 'bookable' => $product->getBookable(), + 'unavailable' => 0 === count($allDates), + 'product' => $product, + 'hotel' => $hotel, + 'altProductLink' => $altProductLink, + 'altLabel' => $altLabel, + 'hasIncludedServices' => $hasIncludedServices, + 'hasSurcharge' => $hasSurchargeOrDiscount['surcharge'], + 'hasDiscount' => $hasSurchargeOrDiscount['discount'], + 'isProductWithNoInfoConcept' => $isProductWithNoInfoConcept, + 'nonBookableInfo' => $nonBookableInfo, + 'myEpUrl' => $myEpUrl, + ]); + } + + /** + * @param Product $product + * @param Hotel $hotel + * @param Date $date + * @param bool $singleDate + */ + public function pricetableAction( + Product $product, + Hotel $hotel, + Date $date, + bool $singleDate = false + ): void { + $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); + $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids); + $myEpUrl = $this->myEpUrlResolverService->resolve(); + + $priceTable = $this->dateService->getPriceTable([ + 'product' => $product, + 'hotel' => $hotel, + 'filterSettings' => $filterSettings, + 'date' => $date, + 'myEpUrl' => $myEpUrl, + 'paCode' => GeneralUtility::_GET('pa'), + ]); + $hasSurchargeOrDiscount = $this->dateService->checkSurcharcheOrDiscount($priceTable); + $hasIncludedServices = $this->dateService->hasIncludedServices($priceTable); + + $dateStart = $date ? $date->getDateStart()->format('d.m.y') : ''; + $dateEnd = $date ? $date->getDateEnd()->format('d.m.y') : ''; + $dateRange = sprintf('%s - %s', $dateStart, $dateEnd); + + $noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids']); + $isProductWithNoInfoConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(), $noInfoConceptUids); + + $nonBookableInfo = $this->getNonBookableInfobox($product); + + $this->view->assignMultiple([ + 'dateRange' => $dateRange, + 'product' => $product, + 'hotel' => $hotel, + 'servicesIncluded' => $date->getServicesIncluded(), + 'prices' => $priceTable, + 'unavailable' => 0 === count($priceTable), + 'hasIncludedServices' => $hasIncludedServices, + 'hasSurcharge' => $hasSurchargeOrDiscount['surcharge'], + 'hasDiscount' => $hasSurchargeOrDiscount['discount'], + 'isProductWithNoInfoConcept' => $isProductWithNoInfoConcept, + 'isSingleDate' => $singleDate, + 'nonBookableInfo' => $nonBookableInfo, + 'myEpUrl' => $myEpUrl, + ]); + } + + /** + * @param Product $product + * @param Hotel $hotel + */ + public function pricetableHtmlAction(Product $product, Hotel $hotel): void + { + $nonBookableInfo = $this->getNonBookableInfobox($product); + $myEpUrl = $this->myEpUrlResolverService->resolve(); + + $priceTable = $this->dateService->getPriceTable([ + 'product' => $product, + 'hotel' => $hotel, + 'filterSettings' => $this->filterService->getNormalizedFilterSettings(), + 'myEpUrl' => $myEpUrl, + 'paCode' => GeneralUtility::_GET('pa'), + 'sortByRoom' => true, + ]); + $hasIncludedServices = $this->dateService->hasIncludedServices($priceTable); + + $this->view->assignMultiple([ + 'pricetable' => $priceTable, + 'hasIncludedServices' => $hasIncludedServices, + 'nonBookableInfo' => $nonBookableInfo, + 'myEpUrl' => $myEpUrl, + ]); + } + + /** + * @param Product $product + */ + public function eventPricetableAction(Product $product): void + { + $roomTypes = GeneralUtility::trimExplode(',', $this->settings['priceTableRoomTypes']); + $roomTypesPax = array_map(function ($type) { + return (int) $type; + }, $roomTypes); + $roomTypesLabels = array_map(function ($type) { + $label = (int) $type . 'er'; + if (false !== strpos($type, '+')) { + $label .= '+'; + } + return $label; + }, $roomTypes); + $maxPax = max($roomTypesPax); + $myEpUrl = $this->myEpUrlResolverService->resolve(); + $priceTable = $this->dateService->getEventPriceTable($product, $maxPax, $myEpUrl); + $hasIncludedServices = $this->dateService->hasIncludedServices($priceTable); + $nonBookableInfo = $this->getNonBookableInfobox($product); + $categories = [ + Hotel::CATEGORY_STANDARD => 'Standard', + Hotel::CATEGORY_COMFORT => 'Komfort', + Hotel::CATEGORY_DELUXE => 'Deluxe', + Hotel::CATEGORY_SUPER_DELUXE => 'Super Deluxe', + ]; + $this->view->assignMultiple([ + 'roomTypesPax' => $roomTypesPax, + 'roomTypesLabels' => $roomTypesLabels, + 'pricetable' => $priceTable, + 'hasIncludedServices' => $hasIncludedServices, + 'categories' => $categories, + 'themekey' => $this->settings['themekey'], + 'nonBookableInfo' => $nonBookableInfo, + 'myEpUrl' => $myEpUrl, + ]); + } + + /** + * @param Hotel $hotel + * @param Product $product + * @param string $dateFrom + * @param string $dateTo + */ + public function daytripRoomsAction( + Hotel $hotel, + Product $product, + string $dateFrom, + string $dateTo + ): void { + $period = new Period($dateFrom, $dateTo); + $nights = (int) $period->dateInterval()->format('%a') + 1; + $myEpUrl = $this->myEpUrlResolverService->resolve(); + + if (2 > $nights) { + $rooms = []; + } else { + $rooms = $this + ->contingentDataService + ->getAvailableRooms($dateFrom, $dateTo, $hotel, $product, $myEpUrl); + } + + $this->view->assign('nights', $nights); + $this->view->assign('rooms', $rooms); + $this->view->assign('myEpUrl', $myEpUrl); + } + + /** + * @param Product $product + * @return \EP\EpProducts\Domain\Model\Infobox + */ + protected function getNonBookableInfobox(Product $product) + { + if (null !== $nonBookableInfo = $product->getNonBookableInfo()) { + return $nonBookableInfo; + } + + return $this->infoboxRepository->getDefault(); + } +} diff --git a/packages/ep_products/Classes/Controller/AjaxTeaserPopupController.php b/packages/ep_products/Classes/Controller/AjaxTeaserPopupController.php new file mode 100644 index 0000000..a97b2e3 --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxTeaserPopupController.php @@ -0,0 +1,17 @@ +view->assignMultiple([ + 'concept' => $concept, + 'targetUrl' => $targetUrl, + ]); + } +} diff --git a/packages/ep_products/Classes/Controller/AjaxWatchlistController.php b/packages/ep_products/Classes/Controller/AjaxWatchlistController.php new file mode 100644 index 0000000..396939a --- /dev/null +++ b/packages/ep_products/Classes/Controller/AjaxWatchlistController.php @@ -0,0 +1,104 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\SearchResultService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class AjaxWatchlistController extends ActionController +{ + + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @var SearchResultService + */ + protected $searchResultService; + + /** + * @var FilterService + */ + protected $filterService; + + /** + * @param ProductRepository $productRepository + * @param SearchResultService $searchResultService + * @param FilterService $filterService + */ + public function __construct + ( + ProductRepository $productRepository, + SearchResultService $searchResultService, + FilterService $filterService + ) + { + $this->productRepository = $productRepository; + $this->searchResultService = $searchResultService; + $this->filterService = $filterService; + } + + public function initializeAction() + { + if ($this->request->hasArgument('watchlistUids')) { + $watchlistUids = json_decode($this->request->getArgument('watchlistUids'), true, 512, JSON_THROW_ON_ERROR); + $this->request->setArgument('watchlistUids', $watchlistUids); + } + } + + /** + * @param array $watchlistUids + * @return string + * @throws \Doctrine\DBAL\DBALException + */ + public function listAction(array $watchlistUids = []) + { + $productUids = array_map(function ($uid) { + [ , $productUid ] = GeneralUtility::trimExplode(':', $uid); + return $productUid; + }, $watchlistUids); + + $hotelUids = array_map(function ($uid) { + [ $hotelUid, ] = GeneralUtility::trimExplode(':', $uid); + return $hotelUid; + }, $watchlistUids); + + $watchlistData = $this->productRepository->getWatchlist($productUids, $hotelUids); + $watchlist = $this->searchResultService->preprocess($watchlistData); + + $this->view->assignMultiple([ + 'watchlist' => $watchlist->getData(), + 'total' => $watchlist->getTotal() + ]); + } +} diff --git a/packages/ep_products/Classes/Controller/BackendController.php b/packages/ep_products/Classes/Controller/BackendController.php new file mode 100644 index 0000000..d92a2a7 --- /dev/null +++ b/packages/ep_products/Classes/Controller/BackendController.php @@ -0,0 +1,149 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\ContingentImportService; +use EP\EpProducts\Service\DateImportService; +use EP\EpProducts\Service\ImportTimestampService; +use EP\EpProducts\Service\LogService; +use EP\EpProducts\Service\SnowreportImportService; +use TYPO3\CMS\Backend\View\BackendTemplateView; +use TYPO3\CMS\Core\Messaging\AbstractMessage; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class BackendController extends ActionController +{ + /** + * @var BackendTemplateView + */ + protected $defaultViewObjectName = BackendTemplateView::class; + + /** + * @var BackendTemplateView + */ + protected $view; + + /** + * @var \EP\EpProducts\Service\DateImportService + */ + protected $dateImportService; + + /** + * @var \EP\EpProducts\Service\SnowreportImportService + */ + protected $snowreportImportService; + + /** + * @var \EP\EpProducts\Service\LogService + */ + protected $logService; + + /** + * @var ContingentImportService + */ + protected $contingentImportService; + + /** + * @param DateImportService $dateImportService + * @param ContingentImportService $contingentImportService + * @param SnowreportImportService $snowreportService + * @param LogService $logService + */ + public function __construct + ( + DateImportService $dateImportService, + ContingentImportService $contingentImportService, + SnowreportImportService $snowreportService, + LogService $logService + ) + { + $this->dateImportService = $dateImportService; + $this->contingentImportService = $contingentImportService; + $this->snowreportImportService = $snowreportService; + $this->logService = $logService; + } + + public function indexAction() + { + $timestampService = new ImportTimestampService(); + + $productDataUpdatedTimestamp = $timestampService->readImportTimestamp('fileadmin/products_import.info'); + $contingentsDataUpdatedTimestamp = $timestampService->readImportTimestamp('fileadmin/contingents_import.info'); + + $this->view->assign('productDataUpdatedTimestamp', $productDataUpdatedTimestamp); + $this->view->assign('contingentsDataUpdatedTimestamp', $contingentsDataUpdatedTimestamp); + } + + public function importSnowReportAction() + { + $this->snowreportImportService->downloadReportsXml(); + $count = $this->snowreportImportService->importSnowReportData(); + $this->addFlashMessage($count . ' Datensätze importiert', 'Import abgechlossen', AbstractMessage::OK); + $this->redirect('index'); + } + + public function dumpSearchLogEpreisenAction() + { + $csv = $this->logService->dumpSearchLogEpreisen(); + $this->dumpSearchLog($csv, 'epreisen'); + } + + public function dumpSearchLogEpeventsAction() + { + $csv = $this->logService->dumpSearchLogEpevents(); + $this->dumpSearchLog($csv, 'epevents'); + } + + /** + * @param array $csv + * @param $name + */ + private function dumpSearchLog(array $csv, $name) + { + $fileName = sprintf('searchlog_dump_%s_%s.csv', $name, date('dmY')); + + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Content-Description: File Transfer'); + header('Content-type: text/csv'); + header("Content-Disposition: attachment; filename={$fileName}"); + header('Expires: 0'); + header('Pragma: public'); + + $fh = @fopen( 'php://output', 'wb' ); + + foreach ($csv as $data) { + fputcsv($fh, $data, ';'); + } + + fclose($fh); + + exit; + } + +} diff --git a/packages/ep_products/Classes/Controller/CityController.php b/packages/ep_products/Classes/Controller/CityController.php new file mode 100644 index 0000000..2e3c869 --- /dev/null +++ b/packages/ep_products/Classes/Controller/CityController.php @@ -0,0 +1,97 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\City; +use EP\EpProducts\Domain\Repository\CityRepository; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class CityController extends ActionController +{ + + /** + * @var CityRepository + */ + protected $cityRepository; + + /** + * @var ProductDataService + */ + private $productDataService; + + /** + * @param CityRepository $cityRepository + * @param ProductDataService $productDataService + */ + public function __construct(CityRepository $cityRepository, ProductDataService $productDataService) + { + $this->cityRepository = $cityRepository; + $this->productDataService = $productDataService; + } + + /** + * @param City $city + */ + public function detailAction(City $city = null): void + { + if (null === $city) { + $cityUid = $this->settings['cityUid']; + $city = $this->cityRepository->findByIdentifier($cityUid); + } + + $teasers = []; + + if (null !== $city) { + $filterSettings = [ + 'cityUids' => [$cityUid] + ]; + $teasers = $this->productDataService->getTeasergroup($filterSettings); + } + + $this->view->assignMultiple([ + 'city' => $city, + 'teasers' => $teasers, + 'filterSettings' => $filterSettings, + ]); + } + + public function teasergroupAction(): void + { + $regionUid = (int)$this->settings['regionUid']; + $countryUid = (int)$this->settings['countryUid']; + $season = $this->settings['season']; + $citiesPid = (int)$this->settings['citiesStoragePid']; + + $cities = $this->cityRepository->getTeasersByCountryOrRegionAndSeason($regionUid, $countryUid, $season, $citiesPid); + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('teasers', $cities); + } + +} diff --git a/packages/ep_products/Classes/Controller/ConceptController.php b/packages/ep_products/Classes/Controller/ConceptController.php new file mode 100644 index 0000000..82a3796 --- /dev/null +++ b/packages/ep_products/Classes/Controller/ConceptController.php @@ -0,0 +1,104 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Concept; +use EP\EpProducts\Domain\Repository\ConceptRepository; +use EP\EpProducts\Service\ProductDataService; +use EP\EpTheme\Domain\Repository\PageRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class ConceptController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\ConceptRepository + */ + protected $conceptRepository; + + /** + * @var \EP\EpProducts\Service\ProductDataService + */ + protected $productDataService; + + /** + * @var \EP\EpTheme\Domain\Repository\PageRepository + */ + private $pageRepository; + + /** + * @param ConceptRepository $conceptRepository + * @param ProductDataService $productDataService + * @param PageRepository $pageRepository + */ + public function __construct(ConceptRepository $conceptRepository, ProductDataService $productDataService, PageRepository $pageRepository) + { + $this->conceptRepository = $conceptRepository; + $this->productDataService = $productDataService; + $this->pageRepository = $pageRepository; + } + + /** + * @param Concept $concept + */ + public function detailAction(Concept $concept = null) + { + if ($concept === null) { + $conceptUid = $this->settings['conceptUid']; + $concept = $this->conceptRepository->findByIdentifier($conceptUid); + } + + $teaserConceptUids = GeneralUtility::trimExplode(',', $this->settings['additionalConceptUids']); + if (null !== $concept) { + array_unshift($teaserConceptUids, $concept->getUid()); + } + + $filterSettings = ['conceptUids' => $teaserConceptUids]; + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $limit = $this->settings['showMoreLink'] ? 6 : 0; + $teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit); + $pages = $this->pageRepository->findPagesByPid((int)$this->settings['conceptsPageUid']); + $data = $this->configurationManager->getContentObject()->data; + + $this->view->assignMultiple([ + 'teasers' => $teasers, + 'concept' => $concept, + 'filterSettings' => $filterSettings, + 'pages' => $pages, + 'data' => $data, + ]); + } + + public function teasergroupAction() + { + $concepts = $this->conceptRepository->findAll(); + $this->view->assign('teasers', $concepts); + } + +} diff --git a/packages/ep_products/Classes/Controller/CountryController.php b/packages/ep_products/Classes/Controller/CountryController.php new file mode 100644 index 0000000..89cf743 --- /dev/null +++ b/packages/ep_products/Classes/Controller/CountryController.php @@ -0,0 +1,79 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Country; +use EP\EpProducts\Domain\Repository\CountryRepository; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class CountryController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\CountryRepository + */ + protected $countryRepository; + + /** + * @var ProductDataService + */ + protected $productDataService; + + /** + * @param CountryRepository $countryRepository + * @param ProductDataService $productDataService + */ + public function __construct(CountryRepository $countryRepository, ProductDataService $productDataService) + { + $this->countryRepository = $countryRepository; + $this->productDataService = $productDataService; + } + + /** + * @param Country $country + */ + public function detailAction(Country $country = null) + { + if ($country === null) { + $countryUid = $this->settings['countryUid']; + $country = $this->countryRepository->findByIdentifier($countryUid); + } + $filterSettings = [ + 'countryUids' => [ $countryUid ], + ]; + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $limit = $this->settings['showMoreLink'] ? 6 : 0; + $teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit); + $this->view->assign('teasers', $teasers); + $this->view->assign('country', $country); + $this->view->assign('filterSettings', $filterSettings); + } + +} diff --git a/packages/ep_products/Classes/Controller/FaqController.php b/packages/ep_products/Classes/Controller/FaqController.php new file mode 100644 index 0000000..d919da1 --- /dev/null +++ b/packages/ep_products/Classes/Controller/FaqController.php @@ -0,0 +1,56 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Repository\FaqRepository; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class FaqController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\FaqRepository + */ + protected $faqRepository; + + /** + * @param FaqRepository $faqRepository + */ + public function __construct(FaqRepository $faqRepository) + { + $this->faqRepository = $faqRepository; + } + + public function detailAction() + { + $faqUids = $this->settings['faqUids']; + $faqs = $this->faqRepository->findByUids($faqUids); + $this->view->assign('faqs', $faqs); + } + +} diff --git a/packages/ep_products/Classes/Controller/HotelController.php b/packages/ep_products/Classes/Controller/HotelController.php new file mode 100644 index 0000000..ce1d22b --- /dev/null +++ b/packages/ep_products/Classes/Controller/HotelController.php @@ -0,0 +1,152 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Repository\HotelRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Service\HotelDataService; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class HotelController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\HotelRepository + */ + protected $hotelRepository; + + /** + * @var \EP\EpProducts\Domain\Repository\ProductRepository + */ + protected $productRepository; + + /** + * @var \EP\EpProducts\Service\HotelDataService + */ + protected $hotelDataService; + + /** + * @var ProductDataService + */ + protected $productDataService; + + /** + * @param HotelDataService $hotelDataService + * @param HotelRepository $hotelRepository + * @param ProductRepository $productRepository + * @param ProductDataService $productDataService + */ + public function __construct + ( + HotelDataService $hotelDataService, + HotelRepository $hotelRepository, + ProductRepository $productRepository, + ProductDataService $productDataService + ) + { + $this->hotelRepository = $hotelRepository; + $this->productRepository = $productRepository; + $this->hotelDataService = $hotelDataService; + $this->productDataService = $productDataService; + } + + /** + * @param Hotel $hotel + */ + public function detailAction(Hotel $hotel = null) + { + if ($hotel !== null) { + $hotelUid = $hotel->getUid(); + } else { + $hotelUid = $this->settings['hotelUid']; + /** @var Hotel $hotel */ + $hotel = $this->hotelRepository->findByIdentifier($hotelUid); + } + $filterSettings = [ + 'hotelUid' => $hotelUid, + ]; + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $limit = $this->settings['hideMoreLink'] ? 0 : 6; + $teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit); + $this->view->assign('hotel', $hotel); + $this->view->assign('teasers', $teasers); + $this->view->assign('filterSettings', $filterSettings); + } + + public function detailEventAction() + { + $productUid = $this->settings['productUid']; + /** @var \EP\EpProducts\Domain\Model\Product $product */ + $product = $this->productRepository->findByIdentifier($productUid); + $hotelUid = $this->settings['hotelUid']; + /** @var \EP\EpProducts\Domain\Model\Hotel $hotel */ + $hotel = $this->hotelRepository->findByIdentifier($hotelUid); + $this->view->assignMultiple([ + 'product' => $product, + 'hotel' => $hotel, + ]); + } + + public function teasergroupAction() + { + $criteria = [ + 'regionUid' => (int) $this->settings['regionUid'], + 'countryUid' => (int) $this->settings['countryUid'], + 'conceptUid' => (int) $this->settings['conceptUid'], + 'productUid' => (int) $this->settings['productUid'], + 'category' => (int) $this->settings['category'], + 'type' => (int) $this->settings['type'], + ]; + $bookable = $this->hotelDataService->getTeasersByCriteria($criteria); + $bookableHotelUids = array_keys($bookable); + $nonBookable = $this->hotelDataService->getDateIndependentTeasersByCriteria($criteria, $bookableHotelUids); + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('bookable', $bookable); + $this->view->assign('nonBookable', $nonBookable); + } + + public function dateIndependentTeasergroupAction() + { + $criteria = [ + 'regionUid' => (int) $this->settings['regionUid'], + 'countryUid' => (int) $this->settings['countryUid'], + 'productUid' => (int) $this->settings['productUid'], + 'category' => (int) $this->settings['category'], + 'type' => (int) $this->settings['type'], + ]; + $hotels = $this->hotelDataService->getDateIndependentTeasersByCriteria($criteria); + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('teasers', $hotels); + } + +} diff --git a/packages/ep_products/Classes/Controller/IcalController.php b/packages/ep_products/Classes/Controller/IcalController.php new file mode 100644 index 0000000..46675b0 --- /dev/null +++ b/packages/ep_products/Classes/Controller/IcalController.php @@ -0,0 +1,56 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Service\IcalService; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class IcalController extends ActionController +{ + /** + * @var IcalService + */ + protected $icalService; + + public function __construct(IcalService $icalService) + { + $this->icalService = $icalService; + } + + /** + * @param Hotel $hotel + * @return string + */ + public function contingentsAction(Hotel $hotel) + { + header(sprintf('Content-disposition: attachment; filename=%s.ical', $hotel->getPathSegment())); + + return $this->icalService->createContingentsFeed($hotel); + } +} diff --git a/packages/ep_products/Classes/Controller/MapController.php b/packages/ep_products/Classes/Controller/MapController.php new file mode 100644 index 0000000..612f6f7 --- /dev/null +++ b/packages/ep_products/Classes/Controller/MapController.php @@ -0,0 +1,123 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Repository\HotelRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +class MapController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\HotelRepository + */ + protected $hotelRepository; + + /** + * @var \EP\EpProducts\Domain\Repository\ProductRepository + */ + protected $productRepository; + + /** + * @param HotelRepository $hotelRepository + * @param ProductRepository $productRepository + */ + public function __construct( + HotelRepository $hotelRepository, + ProductRepository $productRepository + ) + { + $this->hotelRepository = $hotelRepository; + $this->productRepository = $productRepository; + } + + public function generalAction() + { + $markerData = []; + $selectedHotels = $this->settings['hotelUids']; + $hotelUids = GeneralUtility::trimExplode(',', $selectedHotels); + $hotels = $this->hotelRepository->getListByUids($hotelUids); + if ($hotels) { + foreach ($hotels as $hotel) + { + /** @var \EP\EpProducts\Domain\Model\Hotel $hotel */ + $markerData[] = [ + 'name' => $hotel->getName(), + 'uri' => $this->getUriForPageUid($hotel->getDetailPage()), + 'teaser' => $hotel->getTeaser(), + 'lat' => $hotel->getLatitude(), + 'lng' => $hotel->getLongitude(), + ]; + } + } + + $selectedProducts = $this->settings['productUids']; + $productUids = GeneralUtility::trimExplode(',', $selectedProducts); + $products = $this->productRepository->getListByUids($productUids); + if ($products) { + foreach ($products as $product) + { + /** @var \EP\EpProducts\Domain\Model\Product $product */ + $hotel = $product->getHotels()->current(); + if (null === $hotel || false === $hotel->isShowAsTeaser()) { + continue; + } + $markerData[] = [ + 'name' => $product->getName(), + 'uri' => $this->getUriForPageUid($product->getDetailPage()), + 'externalLink' => $hotel->getExternalLink(), + 'teaser' => preg_replace("/[\r\n\t]/", "", $product->getTeaser()), + 'lat' => $hotel->getLatitude(), + 'lng' => $hotel->getLongitude(), + ]; + } + } + + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('markerData', json_encode($markerData, JSON_THROW_ON_ERROR | JSON_HEX_APOS)); + } + + /** + * @param int $pageUid + * @return string + */ + protected function getUriForPageUid($pageUid) + { + /** @var ContentObjectRenderer $contentObject */ + $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class); + return $contentObject->typoLink_URL( + [ + 'parameter' => $pageUid, + ] + ); + } +} diff --git a/packages/ep_products/Classes/Controller/NewsletterController.php b/packages/ep_products/Classes/Controller/NewsletterController.php new file mode 100644 index 0000000..07c4ae4 --- /dev/null +++ b/packages/ep_products/Classes/Controller/NewsletterController.php @@ -0,0 +1,80 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Dto\NewsletterSubscriptionRequest; +use EP\EpProducts\MyEP\ApiClient; +use EP\EpProducts\MyEP\ApiException; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class NewsletterController extends ActionController +{ + private ApiClient $apiClient; + + public function __construct(ApiClient $apiClient) + { + $this->apiClient = $apiClient; + } + + public function indexAction() + { + $this->view->assign('result', GeneralUtility::_GET('result')); + } + + public function subscriptionFormAction(?NewsletterSubscriptionRequest $subscriptionRequest = null) + { + if (null === $subscriptionRequest) { + $subscriptionRequest = new NewsletterSubscriptionRequest(); + } + + try { + $lists = $this->apiClient->getNewsletterLists(); + } catch (ApiException $e) { + $lists = []; + } + + $this->view->assign('subscriptionRequest', $subscriptionRequest); + $this->view->assign('lists', $lists); + } + + public function subscriptionFormSubmitAction(NewsletterSubscriptionRequest $subscriptionRequest) + { + try { + $result = $this->apiClient->subscribeToNewsletters($subscriptionRequest); + $result['success'] = true; + } catch (ApiException $e) { + $result = [ + 'success' => false, + 'message' => $e->getMessage(), + ]; + } + + $this->view->assign('result', $result); + } +} diff --git a/packages/ep_products/Classes/Controller/PricetableController.php b/packages/ep_products/Classes/Controller/PricetableController.php new file mode 100644 index 0000000..3271299 --- /dev/null +++ b/packages/ep_products/Classes/Controller/PricetableController.php @@ -0,0 +1,41 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class PricetableController extends ActionController +{ + + public function eventAction() + { + $productUid = $this->settings['productUid']; + $this->view->assign('productUid', $productUid); + } + +} diff --git a/packages/ep_products/Classes/Controller/ProductController.php b/packages/ep_products/Classes/Controller/ProductController.php new file mode 100644 index 0000000..efb7411 --- /dev/null +++ b/packages/ep_products/Classes/Controller/ProductController.php @@ -0,0 +1,261 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\HotelRepository; +use EP\EpProducts\Domain\Repository\InfoboxRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Service\ContingentDataService; +use EP\EpProducts\Service\DateService; +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\HotelDataService; +use EP\EpProducts\Service\PickupOverviewService; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class ProductController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\ProductRepository + */ + protected $productRepository; + + /** + * @var \EP\EpProducts\Domain\Repository\HotelRepository + */ + protected $hotelRepository; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var \EP\EpProducts\Service\ProductDataService + */ + protected $productDataService; + + /** + * @var DateService + */ + protected $dateService; + + /** + * @var HotelDataService + */ + protected $hotelDataService; + /** + * @var ContingentDataService + */ + private $contingentDataService; + + /** + * @var InfoboxRepository + */ + private $infoboxRepository; + + /** + * @var PickupOverviewService + */ + private $pickupOverviewService; + + /** + * @param ProductRepository $productRepository + * @param ProductDataService $productDataService + * @param HotelRepository $hotelRepository + * @param HotelDataService $hotelDataService + * @param ContingentDataService $contingentDataService + * @param DateService $dateService + * @param FilterService $filterService + * @param InfoboxRepository $infoboxRepository + * @param PickupOverviewService $pickupOverviewService + */ + public function __construct( + ProductRepository $productRepository, + ProductDataService $productDataService, + HotelRepository $hotelRepository, + HotelDataService $hotelDataService, + ContingentDataService $contingentDataService, + DateService $dateService, + FilterService $filterService, + InfoboxRepository $infoboxRepository, + PickupOverviewService $pickupOverviewService + ) { + $this->productRepository = $productRepository; + $this->productDataService = $productDataService; + $this->hotelRepository = $hotelRepository; + $this->hotelDataService = $hotelDataService; + $this->contingentDataService = $contingentDataService; + $this->dateService = $dateService; + $this->filterService = $filterService; + $this->infoboxRepository = $infoboxRepository; + $this->pickupOverviewService = $pickupOverviewService; + } + + /** + * @param Product $product + * @param Hotel $hotel + * @param string $dateFrom + * @param string $dateTo + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException + */ + public function detailAction( + Product $product = null, + Hotel $hotel = null, + $dateFrom = null, + $dateTo = null + ): void { + // Get product to display from flexform settings if not provided via url + if ($product === null) { + $productUid = $this->settings['productUid']; + $product = $this->productRepository->findByIdentifier($productUid); + } + // Redirect to search page in case no product can be determined + if ($product === null) { + $filterSettings = $this->filterService->getNormalizedFilterSettings(); + $this->redirect( + 'searchresult', + 'Search', + null, + ['filterSettings' => $filterSettings], + $this->settings['defaultSearchPageUid'] + ); + } + // Get hotel to display from flexform settings if not provided via url + if ((int)$this->settings['hotelUid'] > 0) { + $hotelUid = $this->settings['hotelUid']; + $hotel = $this->hotelRepository->findByIdentifier($hotelUid); + } + if ($hotel === null) { + // Forward to hotel list action in case multiple hotels are assigned to this product + if ($product->getHotels()->count() > 1) { + $arguments = [ + 'product' => $product, + ]; + $this->forward('hotelList', null, null, $arguments); + } + // Use first hotel which should be the only one at this point + if ($product->getHotels()->count() > 0) { + $product->getHotels()->rewind(); + $hotel = $product->getHotels()->current(); + } + } + + if ($product->isDaytrip()) { + $availableContingents = $this + ->contingentDataService + ->getAvailableContingents($hotel, $product) + ; + $enabledDates = array_keys($availableContingents); + } + + $concept = $product->getConcept(); + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $isProductWithExcludedConcept = ($concept !== null) && \in_array($concept->getUid(), $excludedConceptUids); + $noInfoConceptUids = GeneralUtility::trimExplode(',', $this->settings['noInfoConceptUids'], true); + $isProductWithNoInfoConcept = ($concept !== null) && \in_array($concept->getUid(), $noInfoConceptUids); + $groupInquiryFormConceptUids = GeneralUtility::trimExplode(',', $this->settings['groupInquiryFormConceptUids'], true); + $isProductWithGroupInquiryForm = ($concept !== null) && \in_array($concept->getUid(), $groupInquiryFormConceptUids); + $nonBookableInfo = $product->getNonBookableInfo(); + + // Assemble backlink + $backlinkUrl = GeneralUtility::_GET('ref'); + $backLinkLabel = strpos($backlinkUrl, 'suche') !== -1 ? 'zurück zum Suchergebnis' : 'zurück'; + + $this->view->assignMultiple([ + 'hotel' => $hotel, + 'product' => $product, + 'enabledDates' => $enabledDates ?? [], + 'isProductWithExcludedConcept' => $isProductWithExcludedConcept, + 'isProductWithNoInfoConcept' => $isProductWithNoInfoConcept, + 'isProductWithGroupInquiryForm' => $isProductWithGroupInquiryForm, + 'currentPageUid' => $GLOBALS['TSFE']->id, + 'dateFrom' => $dateFrom, + 'dateTo' => $dateTo, + 'referringUrl' => $backlinkUrl, + 'backlinkLabel' => $backLinkLabel, + 'nonBookableInfo' => $nonBookableInfo, + 'pickupsOverview' => $this->pickupOverviewService->getOverview($product, $hotel), + ]); + } + + public function pricetableAction(): void + { + $productUid = $this->settings['productUid']; + /** @var \EP\EpProducts\Domain\Model\Product $product */ + $product = $this->productRepository->findByIdentifier($productUid); + $hotelUid = $this->settings['hotelUid']; + /** @var \EP\EpProducts\Domain\Model\Hotel $hotel */ + $hotel = $this->hotelRepository->findByIdentifier($hotelUid); + $pricetable = $this->dateService->getPriceTable([ + 'product' => $product, + 'hotel' => $hotel, + 'filterSettings' => $this->filterService->getNormalizedFilterSettings(), + ]); + $this->view->assign('pricetable', $pricetable); + } + + /** + * @param Product $product + */ + public function hotelListAction(Product $product): void + { + $hotels = $this->hotelDataService->getList($product); + $this->view->assignMultiple([ + 'hotels' => $hotels, + 'product' => $product, + 'linkHotel' => true, + ]); + } + + public function teasergroupAction(): void + { + $filterSettings = $this->filterService->getFiltersettingsFromSettings($this->settings); + $dateAuto = !$this->settings['dateFrom'] && !$this->settings['dateTo']; + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $limit = $this->settings['hideMoreLink'] ? 0 : 6; + $teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit); + $dateFrom = $this->settings['dateFrom'] ? date('Y-m-d', $this->settings['dateFrom']) : null; + $dateTo = $this->settings['dateTo'] ? date('Y-m-d', $this->settings['dateTo']) : null; + $data = $this->configurationManager->getContentObject()->data; + $nonBookableInfo = $this->settings['infoboxUid'] ? $this->infoboxRepository->findByUid($this->settings['infoboxUid']) : $this->infoboxRepository->getDefault(); + + $this->view->assign('data', $data); + $this->view->assign('teasers', $teasers); + $this->view->assign('dateAuto', $dateAuto); + $this->view->assign('filterSettings', $filterSettings); + $this->view->assign('dateFrom', $dateFrom); + $this->view->assign('dateTo', $dateTo); + $this->view->assign('nonBookableInfo', $nonBookableInfo); + } +} diff --git a/packages/ep_products/Classes/Controller/RegionController.php b/packages/ep_products/Classes/Controller/RegionController.php new file mode 100644 index 0000000..e605507 --- /dev/null +++ b/packages/ep_products/Classes/Controller/RegionController.php @@ -0,0 +1,95 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Region; +use EP\EpProducts\Domain\Repository\RegionRepository; +use EP\EpProducts\Service\ProductDataService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class RegionController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\RegionRepository + */ + protected $regionRepository; + + /** + * @var \EP\EpProducts\Service\ProductDataService + */ + protected $productDataService; + + /** + * @param RegionRepository $regionRepository + * @param ProductDataService $productDataService + */ + public function __construct(RegionRepository $regionRepository, ProductDataService $productDataService) + { + $this->regionRepository = $regionRepository; + $this->productDataService = $productDataService; + } + + /** + * @param Region $region + */ + public function detailAction(Region $region = null) + { + if ($region === null) { + $regionUid = $this->settings['regionUid']; + $region = $this->regionRepository->findByIdentifier($regionUid); + } + $additionalRegionUids = GeneralUtility::trimExplode(',', $this->settings['additionalRegionUids'], true); + $additionalRegionUids = array_map(function ($uid) { + return (int)$uid; + }, $additionalRegionUids); + $teaserRegionUids = array_merge([$region->getUid()], $additionalRegionUids); + $filterSettings = [ + 'regionUids' => $teaserRegionUids, + ]; + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'], true); + $limit = $this->settings['hideMoreLink'] ? 0 : 6; + $teasers = $this->productDataService->getTeasergroup($filterSettings, $excludedConceptUids, $limit); + $this->view->assign('teasers', $teasers); + $this->view->assign('region', $region); + $this->view->assign('filterSettings', $filterSettings); + } + + public function teasergroupAction() + { + $countryUid = (int)$this->settings['countryUid']; + $season = $this->settings['season']; + $regionsPid = (int)$this->settings['regionsStoragePid']; + $regions = $this->regionRepository->getTeasersByCountryAndSeason($countryUid, $season, $regionsPid); + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('teasers', $regions); + } + +} diff --git a/packages/ep_products/Classes/Controller/ResellerController.php b/packages/ep_products/Classes/Controller/ResellerController.php new file mode 100644 index 0000000..24e2f19 --- /dev/null +++ b/packages/ep_products/Classes/Controller/ResellerController.php @@ -0,0 +1,99 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Model\Reseller; +use EP\EpProducts\Service\MyEpUrlResolverService; +use EP\EpProducts\Service\ResellerDataService; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class ResellerController extends ActionController +{ + /** + * @var ResellerDataService + */ + protected $resellerDataService; + + /** + * @var MyEpUrlResolverService + */ + protected $myEpUrlResolverService; + + /** + * @param ResellerDataService $resellerDataService + * @param MyEpUrlResolverService $myEpUrlResolverService + */ + public function __construct( + ResellerDataService $resellerDataService, + MyEpUrlResolverService $myEpUrlResolverService + ) { + $this->resellerDataService = $resellerDataService; + $this->myEpUrlResolverService = $myEpUrlResolverService; + } + + /** + * @param Reseller $reseller + */ + public function listAction(Reseller $reseller = null) + { + $products = []; + + if ($reseller !== null) { + $products = $reseller->getProducts(); + } + + $this->view->assign('reseller', $reseller); + $this->view->assign('products', $products); + } + + /** + * @param Reseller $reseller + * @param Product $product + * @throws \Doctrine\DBAL\DBALException + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException + */ + public function copypasteAction(Reseller $reseller, Product $product) + { + if (!$reseller->getProducts()->contains($product)) { + $this->redirect( + 'list', + null, + null, + [ 'reseller' => $reseller ] + ); + } + + $myEpUrl = $this->myEpUrlResolverService->resolve(); + $productData = $this->resellerDataService->preprocessSingle($product, $reseller, $myEpUrl); + + $this->view->assign('reseller', $reseller); + $this->view->assign('productData', $productData); + } +} diff --git a/packages/ep_products/Classes/Controller/SearchController.php b/packages/ep_products/Classes/Controller/SearchController.php new file mode 100644 index 0000000..dc3d552 --- /dev/null +++ b/packages/ep_products/Classes/Controller/SearchController.php @@ -0,0 +1,168 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\FilterSettingsEncoder; +use EP\EpProducts\Service\SearchResultService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use EP\EpProducts\Utility\DateUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class SearchController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var \EP\EpProducts\Service\FilterService + */ + protected $filterService; + + /** + * @var SearchResultService + */ + private $searchResultService; + + /** + * @param FilterService $filterService + */ + public function __construct(FilterService $filterService, SearchResultService $searchResultService) + { + $this->filterService = $filterService; + $this->searchResultService = $searchResultService; + } + + public function initializeProcessSearchAction() + { + $this->processSearchParamsArgument(); + } + + /** + * @param array $searchParams + */ + public function processSearchAction(array $searchParams) + { + $filterSettings = $this->filterService->getFilterSettingsFromSearchParams($searchParams); + + $redirectUri = $this->uriBuilder + ->reset() + ->setCreateAbsoluteUri(true) + ->uriFor( + 'searchresult', + ['filterSettings' => $filterSettings], + null, + null, + 'searchresult' + ); + + $this->redirectToUri($redirectUri); + } + + public function initializeSearchresultAction() + { + // Process encoded filtersettings from static search result. + // Explicit filter settings win: they carry the visitor's current selection, + // whereas a stale 'f' riding along on a link would silently replace it. + $encodedFilterSettings = GeneralUtility::_GET('f'); + if ($encodedFilterSettings && !$this->request->hasArgument('filterSettings')) { + $filterSettingsEncoder = new FilterSettingsEncoder(); + $filterSettings = $filterSettingsEncoder->decode($encodedFilterSettings); + if (count($filterSettings) > 0) { + $this->request->setArgument('filterSettings', $filterSettings); + } + } + $forcedConceptUids = GeneralUtility::intExplode(',', $this->settings['forcedConceptUids'], true); + $this->processFilterSettingsArgument($forcedConceptUids); + } + + /** + * @param array $filterSettings + */ + public function searchresultAction(array $filterSettings) + { + $dateSelected = false; + $organic = true; + $forcedConceptUids = GeneralUtility::intExplode(',', $this->settings['forcedConceptUids'], true); + $excludedConceptUids = GeneralUtility::intExplode(',', $this->settings['excludedConceptUids']); + + if (!empty($filterSettings['dateFrom']) || !empty($filterSettings['dateTo'])) { + $dateSelected = true; + } + + $searchResult = $this + ->searchResultService + ->getResult($filterSettings, $excludedConceptUids); + + // No results? First retry without date constraint if any + if ((!empty($filterSettings['dateFrom']) || !empty($filterSettings['dateTo'])) && 0 === $searchResult->getTotal()) { + $filterSettings['dateFrom'] = null; + $filterSettings['dateTo'] = null; + $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); + $organic = false; + } + + // Still no results? Remove all constraints and search again with defaults + if (0 === $searchResult->getTotal()) { + $filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids); + $searchResult = $this->searchResultService->getResult($filterSettings, $excludedConceptUids); + $organic = false; + } + + $filterOptions = $this->filterService->collectFilterOptionsFromSearchResult($searchResult); + + $referringPageUri = $this + ->uriBuilder + ->reset() + ->setTargetPageUid($this->settings['defaultSearchPageUid']) + ->setCreateAbsoluteUri(true) + ->uriFor( + 'searchresult', + ['filterSettings' => $filterSettings], + 'Search', + 'epproducts', + 'searchresult' + ) + ; + + $this->view->assignMultiple([ + 'filterSettings' => $filterSettings, + 'filterOptions' => $filterOptions, + 'searchResult' => $searchResult->getData(), + 'referringPageUri' => $referringPageUri, + 'stimulusController' => 'search', + 'meta' => [ + 'organic' => $organic, + 'total' => $searchResult->getTotal(), + 'dateRange' => DateUtility::formatDateRange($filterSettings['dateFrom'], $filterSettings['dateTo']), + 'dateSelected' => $dateSelected, + ], + ]); + } +} diff --git a/packages/ep_products/Classes/Controller/SearchbarController.php b/packages/ep_products/Classes/Controller/SearchbarController.php new file mode 100644 index 0000000..2f9b6d1 --- /dev/null +++ b/packages/ep_products/Classes/Controller/SearchbarController.php @@ -0,0 +1,87 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\FilterService; +use EP\EpProducts\Service\SearchResultService; +use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class SearchbarController extends ActionController +{ + + use RequestArgumentTypeConversionTrait; + + /** + * @var FilterService + */ + protected $filterService; + + /** + * @var SearchResultService + */ + protected $searchResultService; + + /** + * @param FilterService $filterService + * @param SearchResultService $searchResultService + */ + public function __construct(FilterService $filterService, SearchResultService $searchResultService) + { + $this->filterService = $filterService; + $this->searchResultService = $searchResultService; + } + + public function initializeAction() + { + $this->processSearchParamsArgument(); + } + + /** + * @param array $searchParams + */ + public function indexAction(array $searchParams) + { + $forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true); + $filterSettings = $this->filterService->getFilterSettingsFromSearchParams($searchParams, $forcedConceptUids); + $excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']); + + $searchResult = $this + ->searchResultService + ->getResult($filterSettings, $excludedConceptUids); + + $filterOptions = $this->filterService->collectFilterOptionsFromSearchResult($searchResult); + + $this->view->assignMultiple([ + 'filterOptions' => $filterOptions, + 'searchParams' => $searchParams, + 'collapsed' => (bool)$this->settings['collapsed'], + ]); + } +} diff --git a/packages/ep_products/Classes/Controller/SnowreportController.php b/packages/ep_products/Classes/Controller/SnowreportController.php new file mode 100644 index 0000000..10d4a0c --- /dev/null +++ b/packages/ep_products/Classes/Controller/SnowreportController.php @@ -0,0 +1,59 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Repository\SnowreportRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class SnowreportController extends ActionController +{ + + /** + * @var \EP\EpProducts\Domain\Repository\SnowreportRepository + */ + protected $snowreportRepository; + + /** + * @param SnowreportRepository $snowreportRepository + */ + public function __construct(SnowreportRepository $snowreportRepository) + { + $this->snowreportRepository = $snowreportRepository; + } + + public function listAction() + { + $regionUids = GeneralUtility::trimExplode(',', $this->settings['regionUids'], true); + $list = $this->snowreportRepository->getSnowreportsForRegions($regionUids); + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('list', $list); + } + +} diff --git a/packages/ep_products/Classes/Controller/TeamController.php b/packages/ep_products/Classes/Controller/TeamController.php new file mode 100644 index 0000000..0d08fc4 --- /dev/null +++ b/packages/ep_products/Classes/Controller/TeamController.php @@ -0,0 +1,96 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Teammember; +use EP\EpProducts\Domain\Repository\TeammemberRepository; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class TeamController extends ActionController +{ + /** + * @var \EP\EpProducts\Domain\Repository\TeammemberRepository + */ + protected $teammemberRepository; + + /** + * @param TeammemberRepository $teammemberRepository + */ + public function __construct(TeammemberRepository $teammemberRepository) + { + $this->teammemberRepository = $teammemberRepository; + } + + public function listAction() + { + if (isset($this->settings['teamCategories']) && count(GeneralUtility::trimExplode(',', $this->settings['teamCategories'], true)) > 0) { + $members = $this->teammemberRepository->findByCategories(GeneralUtility::trimExplode(',', $this->settings['teamCategories'], true)); + } else { + $members = $this->teammemberRepository->findAll(); + } + + $this->view->assign('members', $members); + } + + /** + * @param Teammember $member + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException + */ + public function showAction(Teammember $member = null) + { + if ($member === null) { + $this->redirect('list', null, null, null, null, 0, 302); + } + + $this->view->assign('member', $member); + $this->view->assign('data', $this->configurationManager->getContentObject()->data); + $this->view->assign('header', $this->coalesceHeader($member)); + } + + /** + * @param Teammember $member + * + * @return string + */ + public function coalesceHeader(Teammember $member) + { + $data = $this->configurationManager->getContentObject()->data; + if (isset($data['header']) && $data['header'] !== '') { + return $data['header']; + } + + $firstCategory = current($member->getCategories()->toArray()); + if ($firstCategory !== null && $firstCategory !== false) { + return $firstCategory->getTitle(); + } + + return ''; + } +} diff --git a/packages/ep_products/Classes/Controller/TravelinfoController.php b/packages/ep_products/Classes/Controller/TravelinfoController.php new file mode 100644 index 0000000..9e880bc --- /dev/null +++ b/packages/ep_products/Classes/Controller/TravelinfoController.php @@ -0,0 +1,105 @@ +apiClient = $apiClient; + $this->travelinfoRepository = $travelinfoRepository; + } + + public function travelCodeAction(string $travelCode) + { + // find specific travel info by code including travel date first + $travelInfo = $this->travelinfoRepository->findOneByTravelCode($travelCode); + + // find general travel info by code without travel date as fallback + if (null === $travelInfo) { + $travelCodeBase = substr($travelCode, 0, -6); + $travelInfo = $this->travelinfoRepository->findOneByTravelCode($travelCodeBase); + } + + // return 404 in case no travel info is available as last resort + if (null === $travelInfo) { + $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction( + $GLOBALS['TYPO3_REQUEST'], + 'Reiseinformationen nicht gefunden' + ); + throw new ImmediateResponseException($response, 1731071856); + } + + $this->handleRequest($travelCode, $travelInfo); + } + + public function travelInfoAction(Travelinfo $travelInfo) + { + $travelCode = $travelInfo->getTravelCode(); + + $this->handleRequest($travelCode, $travelInfo); + } + + private function handleRequest(string $travelCode, Travelinfo $travelInfo) + { + + // fetch travel data via API call + try { + $travelData = $this->apiClient->getTravel($travelCode); + } catch (ApiException $e) { + $travelData = []; + } + + try { + $pickupsPlanning = $this->apiClient->getPickupsPlanning($travelCode); + } catch (ApiException $e) { + $pickupsPlanning = []; + } + + // extract courses from additional services + $courses = array_filter($travelData['additionalServices'] ?? [], function ($service) { + return 'KUR' === $service['subType']; + }); + + // extract board services from included services + $includedServicesBoard = $travelData['selectionGroups'][self::SELECTION_ID_BOARD] ?? []; + $board = $includedServicesBoard['selections'] ?? []; + + // extract bus departure time if applicable + $departureDayTime = null; + $service = array_filter($travelData['transportationServices'] ?? [], function ($service) { + return 'BUS' === $service['subType'] && 'RUECK' === $service['direction']; + }); + if ($serviceDetail = reset($service)) { + $departureDayTime = $serviceDetail['dayTime']; + } + + $this->view->assign('travelInfo', $travelInfo); + $this->view->assign('travelData', $travelData); + $this->view->assign('pickupsPlanning', $pickupsPlanning); + $this->view->assign('courses', $courses); + $this->view->assign('board', $board); + $this->view->assign('departureDayTime', $departureDayTime); + } +} diff --git a/packages/ep_products/Classes/Controller/WatchlistController.php b/packages/ep_products/Classes/Controller/WatchlistController.php new file mode 100644 index 0000000..74f46ac --- /dev/null +++ b/packages/ep_products/Classes/Controller/WatchlistController.php @@ -0,0 +1,42 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +class WatchlistController extends ActionController +{ + + public function listAction() + { + $data = $this->configurationManager->getContentObject()->data; + $this->view->assign('data', $data); + $this->view->assign('referringPageUid', $GLOBALS['TSFE']->id); + } + +} diff --git a/packages/ep_products/Classes/CsvFormatter/FacebookCatalogCsvFormatter.php b/packages/ep_products/Classes/CsvFormatter/FacebookCatalogCsvFormatter.php new file mode 100644 index 0000000..2b86448 --- /dev/null +++ b/packages/ep_products/Classes/CsvFormatter/FacebookCatalogCsvFormatter.php @@ -0,0 +1,66 @@ + 0 ? 'in stock' : 'out of stock', + 'new', + $description, + $product['images']['image'][0], + $product['link'], + $product['name'], + sprintf('%.2f EUR', $minPrice), + 'brand-epreisen' + ]; + } + + return $csv; + } +} diff --git a/packages/ep_products/Classes/CsvFormatter/GoogleBusinessFeedCsvFormatter.php b/packages/ep_products/Classes/CsvFormatter/GoogleBusinessFeedCsvFormatter.php new file mode 100644 index 0000000..9a8e17c --- /dev/null +++ b/packages/ep_products/Classes/CsvFormatter/GoogleBusinessFeedCsvFormatter.php @@ -0,0 +1,58 @@ + 0) { + $csv[] = [ + $product['@id'], + $product['@id'], + $product['name'], + $product['link'], + $product['images']['image'][0], + sprintf('%.2f EUR', $minPrice), + ]; + } + } + + return $csv; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/AbstractDestination.php b/packages/ep_products/Classes/Domain/Model/AbstractDestination.php new file mode 100644 index 0000000..35413e0 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/AbstractDestination.php @@ -0,0 +1,420 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class AbstractDestination extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + const SEASON_WINTER = 'w'; + const SEASON_SUMMER = 's'; + + /** + * @var string + */ + protected $detailPage; + + /** + * @var string + */ + protected $season = self::SEASON_WINTER; + + /** + * @var float + */ + protected $latitude = 0.0; + + /** + * @var float + */ + protected $longitude = 0.0; + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $nameInternal = ''; + + /** + * @var string + */ + protected $keywords = ''; + + /** + * @var string + */ + protected $headline = ''; + + /** + * @var string + */ + protected $teaser = ''; + + /** + * @var string + */ + protected $headerTitle = ''; + + /** + * @var string + */ + protected $headerSubtitle = ''; + + /** + * @var string + */ + protected $description = ''; + + /** + * @var string + */ + protected $descriptionExtended = ''; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $images; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $teaserImages; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $headerImages; + + public function __construct() + { + $this->initStorageObjects(); + } + + protected function initStorageObjects() + { + $this->images = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->teaserImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->headerImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return string + */ + public function getDetailPage() + { + return $this->detailPage; + } + + /** + * @param string $detailPage + */ + public function setDetailPage($detailPage) + { + $this->detailPage = $detailPage; + } + + /** + * @return string + */ + public function getSeason() + { + return $this->season; + } + + /** + * @param string $season + */ + public function setSeason($season) + { + $this->season = $season; + } + + public function isWinter(): bool + { + return self::SEASON_WINTER === $this->season; + } + + public function isSummer(): bool + { + return self::SEASON_SUMMER === $this->season; + } + + /** + * @return float $latitude + */ + public function getLatitude() + { + return $this->latitude; + } + + /** + * @param float $latitude + */ + public function setLatitude($latitude) + { + $this->latitude = $latitude; + } + + /** + * @return float $longitude + */ + public function getLongitude() + { + return $this->longitude; + } + + /** + * @param float $longitude + */ + public function setLongitude($longitude) + { + $this->longitude = $longitude; + } + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getNameInternal() + { + return $this->nameInternal; + } + + /** + * @param string $nameInternal + */ + public function setNameInternal($nameInternal) + { + $this->nameInternal = $nameInternal; + } + + /** + * @return string + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * @param string $keywords + */ + public function setKeywords($keywords) + { + $this->keywords = $keywords; + } + + /** + * @return string + */ + public function getHeadline() + { + return $this->headline; + } + + /** + * @param string $headline + */ + public function setHeadline($headline) + { + $this->headline = $headline; + } + + /** + * @return string + */ + public function getTeaser() + { + return $this->teaser; + } + + /** + * @param string $teaser + */ + public function setTeaser($teaser) + { + $this->teaser = $teaser; + } + + /** + * @return string + */ + public function getHeaderTitle() + { + return $this->headerTitle; + } + + /** + * @param string $headerTitle + */ + public function setHeaderTitle($headerTitle) + { + $this->headerTitle = $headerTitle; + } + + /** + * @return string + */ + public function getHeaderSubtitle() + { + return $this->headerSubtitle; + } + + /** + * @param string $headerSubtitle + */ + public function setHeaderSubtitle($headerSubtitle) + { + $this->headerSubtitle = $headerSubtitle; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return string + */ + public function getDescriptionExtended() + { + return $this->descriptionExtended; + } + + /** + * @param string $descriptionExtended + */ + public function setDescriptionExtended($descriptionExtended) + { + $this->descriptionExtended = $descriptionExtended; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $images + */ + public function getImages() + { + return $this->images; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $images + */ + public function setImages(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $images) + { + $this->images = $images; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImages() + { + return $this->teaserImages; + } + + /** + * @return null|\TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImage() + { + if ($this->teaserImages->count() === 0) { + return null; + } + + return $this->teaserImages->toArray()[0]; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $teaserImages + */ + public function setTeaserImages($teaserImages) + { + $this->teaserImages = $teaserImages; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImages() + { + return $this->headerImages; + } + + /** + * @return null|\TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getHeaderImage() + { + $headerImages = $this->getHeaderImages(); + + if ($headerImages->count() === 0) { + return null; + } + + return $headerImages->toArray()[0]; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $headerImages + */ + public function setHeaderImages($headerImages) + { + $this->headerImages = $headerImages; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Badge.php b/packages/ep_products/Classes/Domain/Model/Badge.php new file mode 100644 index 0000000..65cc14c --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Badge.php @@ -0,0 +1,97 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Badge extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $title; + + /** + * @var string + */ + protected $subtitle; + + /** + * @var string + */ + protected $link; + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @return string + */ + public function getSubtitle() + { + return $this->subtitle; + } + + /** + * @param string $subtitle + */ + public function setSubtitle($subtitle) + { + $this->subtitle = $subtitle; + } + + /** + * @return string + */ + public function getLink() + { + return $this->link; + } + + /** + * @param string $link + */ + public function setLink($link) + { + $this->link = $link; + } + +} + diff --git a/packages/ep_products/Classes/Domain/Model/City.php b/packages/ep_products/Classes/Domain/Model/City.php new file mode 100644 index 0000000..f8c3010 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/City.php @@ -0,0 +1,199 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class City extends AbstractDestination implements TeaserInterface +{ + + /** + * @var bool + */ + protected $excludeFromTeasergroups = false; + + /** + * @var string + */ + protected $party = ''; + + /** + * @var string + */ + protected $leisure = ''; + + /** + * @var \EP\EpProducts\Domain\Model\Country + */ + protected $country; + + /** + * @var \EP\EpProducts\Domain\Model\Region + */ + protected $region; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> + */ + protected $facts; + + /** + * @var \EP\EpProducts\Domain\Model\Officetip + */ + protected $officetip; + + protected function initStorageObjects() + { + parent::initStorageObjects(); + $this->facts = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return bool + */ + public function isExcludeFromTeasergroups() + { + return $this->excludeFromTeasergroups; + } + + /** + * @param bool $excludeFromTeasergroups + */ + public function setExcludeFromTeasergroups($excludeFromTeasergroups) + { + $this->excludeFromTeasergroups = (bool) $excludeFromTeasergroups; + } + + /** + * @return string + */ + public function getParty() { + return $this->party; + } + + /** + * @param string $party + */ + public function setParty($party) { + $this->party = $party; + } + + /** + * @return string + */ + public function getLeisure() { + return $this->leisure; + } + + /** + * @param string $leisure + */ + public function setLeisure($leisure) { + $this->leisure = $leisure; + } + + /** + * Returns all header images with fallback to parent + * region in case none are assigned. + * + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImages() + { + $headerImages = $this->headerImages; + + if ($headerImages->count() === 0 && null !== $this->getRegion()) { + $headerImages = $this->getRegion()->getHeaderImages(); + } + + return $headerImages; + } + + /** + * @return \EP\EpProducts\Domain\Model\Country $country + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param \EP\EpProducts\Domain\Model\Country $country + */ + public function setCountry(\EP\EpProducts\Domain\Model\Country $country) + { + $this->country = $country; + } + + /** + * @return \EP\EpProducts\Domain\Model\Region $region + */ + public function getRegion() + { + return $this->region; + } + + /** + * @param \EP\EpProducts\Domain\Model\Region $region + */ + public function setRegion(\EP\EpProducts\Domain\Model\Region $region) + { + $this->region = $region; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function getFacts() + { + return $this->facts; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function setFacts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $facts) + { + $this->facts = $facts; + } + + /** + * @return Officetip + */ + public function getOfficetip() + { + return $this->officetip; + } + + /** + * @param Officetip $officetip + */ + public function setOfficetip($officetip) + { + $this->officetip = $officetip; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Concept.php b/packages/ep_products/Classes/Domain/Model/Concept.php new file mode 100644 index 0000000..6878b57 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Concept.php @@ -0,0 +1,434 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Concept extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements TeaserInterface +{ + + /** + * @var int + */ + protected $busProId = 0; + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $code = ''; + + /** + * @var string + */ + protected $headline = ''; + + /** + * @var string + */ + protected $headerTitle = ''; + + /** + * @var string + */ + protected $headerSubtitle = ''; + + /** + * @var string + */ + protected $teaser = ''; + + /** + * @var string + */ + protected $popupTitle = 'Hinweis'; + + /** + * @var string + */ + protected $popupText = ''; + + /** + * @var string + */ + protected $popupButtonLabel = 'Ok'; + + /** + * @var string + */ + protected $description = ''; + + /** + * @var string + */ + protected $descriptionExtended = ''; + + /** + * @var string + */ + protected $detailPage; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $teaserImages; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $headerImages; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $images; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Product> + */ + protected $products; + + /** + * __construct + */ + public function __construct() + { + $this->initStorageObjects(); + } + + /** + */ + protected function initStorageObjects() + { + $this->products = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->teaserImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->headerImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->images = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return int + */ + public function getBusProId() { + return $this->busProId; + } + + /** + * @param int $busProId + */ + public function setBusProId($busProId) { + $this->busProId = $busProId; + } + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @return string + */ + public function getTeaser() + { + return $this->teaser; + } + + /** + * @param string $teaser + */ + public function setTeaser($teaser) + { + $this->teaser = $teaser; + } + + public function getPopupText() + { + return $this->popupText; + } + + public function setPopupText($popupText) + { + $this->popupText = $popupText; + + return $this; + } + + public function getPopupTitle() + { + return $this->popupTitle; + } + + public function setPopupTitle($popupTitle) + { + $this->popupTitle = $popupTitle; + + return $this; + } + + public function getPopupButtonLabel() + { + return $this->popupButtonLabel; + } + + public function setPopupButtonLabel($popupButtonLabel) + { + $this->popupButtonLabel = $popupButtonLabel; + + return $this; + } + + /** + * @return string + */ + public function getHeadline() + { + return $this->headline; + } + + /** + * @param string $headline + */ + public function setHeadline($headline) + { + $this->headline = $headline; + } + + /** + * @return string + */ + public function getHeaderTitle() + { + return $this->headerTitle; + } + + /** + * @param string $headerTitle + */ + public function setHeaderTitle($headerTitle) + { + $this->headerTitle = $headerTitle; + } + + /** + * @return string + */ + public function getHeaderSubtitle() + { + return $this->headerSubtitle; + } + + /** + * @param string $headerSubtitle + */ + public function setHeaderSubtitle($headerSubtitle) + { + $this->headerSubtitle = $headerSubtitle; + } + + /** + * @return string $description + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return string + */ + public function getDescriptionExtended() + { + return $this->descriptionExtended; + } + + /** + * @param string $descriptionExtended + */ + public function setDescriptionExtended($descriptionExtended) + { + $this->descriptionExtended = $descriptionExtended; + } + + /** + * @return string $detailPage + */ + public function getDetailPage() + { + return $this->detailPage; + } + + /** + * @param string $detailPage + */ + public function setDetailPage($detailPage) + { + $this->detailPage = $detailPage; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImages() + { + return $this->teaserImages; + } + + /** + * @return null|\TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImage() + { + if ($this->teaserImages->count() === 0) { + return null; + } + + return $this->teaserImages->toArray()[0]; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $teaserImages + */ + public function setTeaserImages($teaserImages) + { + $this->teaserImages = $teaserImages; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImages() + { + return $this->headerImages; + } + + /** + * @return null|\TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImage() + { + if ($this->headerImages->count() === 0) { + return null; + } + + return $this->headerImages->toArray()[0]; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $headerImages + */ + public function setHeaderImages($headerImages) + { + $this->headerImages = $headerImages; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getImages() + { + return $this->images; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $images + */ + public function setImages($images) + { + $this->images = $images; + } + + /** + * @param \EP\EpProducts\Domain\Model\Product $product + */ + public function addProduct(\EP\EpProducts\Domain\Model\Product $product) + { + $this->products->attach($product); + } + + /** + * @param \EP\EpProducts\Domain\Model\Product $product + */ + public function removeProduct(\EP\EpProducts\Domain\Model\Product $product) + { + $this->products->detach($product); + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Product> + */ + public function getProducts() + { + return $this->products; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Product> $products + */ + public function setProducts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $products) + { + $this->products = $products; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Contingent.php b/packages/ep_products/Classes/Domain/Model/Contingent.php new file mode 100644 index 0000000..a6a681a --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Contingent.php @@ -0,0 +1,353 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use CalendR\Event\EventInterface; +use CalendR\Period\PeriodInterface; +use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; + +class Contingent extends AbstractEntity implements EventInterface +{ + const STATUS_OK = 0; + const STATUS_BLOCKED = 1; + const STATUS_ONREQUEST = 2; + + /** + * @var \DateTime + */ + protected $date; + + /** + * @var \EP\EpProducts\Domain\Model\Hotel + */ + protected $hotel; + + /** + * @var string + */ + protected $roomCode; + + /** + * @var string + */ + protected $roomLabel; + + /** + * @var int + */ + protected $pax; + + /** + * @var int + */ + protected $available; + + /** + * @var int + */ + protected $status; + + /** + * @var int + */ + protected $minPrice; + + /** + * @var int + */ + protected $minNights; + + /** + * @var int + */ + protected $additionalNightMinPrice; + + /** + * @var int + */ + protected $additionalNightMinNights; + + /** + * @param array $data + * @return Contingent + */ + public static function fromArray(array $data) + { + $event = new self; + + $event->setPax($data['pax']); + $event->setAvailable($data['available']); + $event->setDate(new \DateTime($data['date'])); + $event->setStatus($data['status']); + + if ($data['hotel'] instanceof Hotel) { + $event->setHotel($data['hotel']); + } + + return $event; + } + + /** + * @return \DateTime + */ + public function getDate() + { + return $this->date; + } + + /** + * @param \DateTime $date + */ + public function setDate(\DateTime $date) + { + $this->date = $date; + } + + /** + * @return Hotel + */ + public function getHotel() + { + return $this->hotel; + } + + /** + * @param Hotel $hotel + */ + public function setHotel($hotel) + { + $this->hotel = $hotel; + } + + /** + * @return string + */ + public function getRoomCode() + { + return $this->roomCode; + } + + /** + * @param string $roomCode + */ + public function setRoomCode($roomCode) + { + $this->roomCode = $roomCode; + } + + /** + * @return string + */ + public function getRoomLabel() + { + return $this->roomLabel; + } + + /** + * @param string $roomLabel + */ + public function setRoomLabel($roomLabel) + { + $this->roomLabel = $roomLabel; + } + + /** + * @return int + */ + public function getPax() + { + return $this->pax; + } + + /** + * @param int $pax + */ + public function setPax($pax) + { + $this->pax = $pax; + } + + /** + * @return int + */ + public function getAvailable() + { + return $this->available; + } + + /** + * @param int $available + */ + public function setAvailable($available) + { + $this->available = $available; + } + + /** + * @return int + */ + public function getPercentageAvailable() + { + if ($this->pax === 0) { + return 0; + } + + return round($this->available / $this->pax * 100, 2); + } + + /** + * @return int + */ + public function getStatus() + { + return $this->status; + } + + /** + * @param int $status + */ + public function setStatus($status) + { + $this->status = $status; + } + + /** + * @return int + */ + public function getMinPrice() + { + return $this->minPrice; + } + + /** + * @param int $minPrice + */ + public function setMinPrice($minPrice) + { + $this->minPrice = $minPrice; + } + + /** + * @return int + */ + public function getMinNights() + { + return $this->minNights; + } + + /** + * @param int $minNights + */ + public function setMinNights($minNights) + { + $this->minNights = $minNights; + } + + /** + * @return int + */ + public function getAdditionalNightMinPrice() + { + return $this->additionalNightMinPrice; + } + + /** + * @param int $additionalNightMinPrice + */ + public function setAdditionalNightMinPrice($additionalNightMinPrice) + { + $this->additionalNightMinPrice = $additionalNightMinPrice; + } + + /** + * @return int + */ + public function getAdditionalNightMinNights() + { + return $this->additionalNightMinNights; + } + + /** + * @param int $additionalNightMinNights + */ + public function setAdditionalNightMinNights($additionalNightMinNights) + { + $this->additionalNightMinNights = $additionalNightMinNights; + } + + /** + * @return \DateTime + */ + public function getBegin() + { + return $this->date; + } + + /** + * @return \DateTime + */ + public function getEnd() + { + return $this->date; + } + + /** + * @param \DateTime $datetime + * @return bool + */ + public function contains(\DateTime $datetime) + { + return $datetime >= $this->getBegin() && $datetime < $this->getEnd(); + } + + /** + * @param PeriodInterface $period + * @return bool + */ + public function containsPeriod(PeriodInterface $period) + { + return $this->contains($period->getBegin()) && $this->contains($period->getEnd()); + } + + /** + * @param PeriodInterface $period + * @return bool + */ + public function isDuring(PeriodInterface $period) + { + return $this->getBegin() >= $period->getBegin() && $this->getEnd() < $period->getEnd(); + } + + /** + * @return int + */ + public function getOccupancy() + { + return $this->getPax() - $this->getAvailable(); + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Country.php b/packages/ep_products/Classes/Domain/Model/Country.php new file mode 100644 index 0000000..043dcaf --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Country.php @@ -0,0 +1,158 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Country extends AbstractDestination implements TeaserInterface +{ + + /** + * @var string + */ + protected $code = ''; + + /** + * @var string + */ + protected $descriptionExtended = ''; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> + */ + protected $facts; + + /** + * @var \EP\EpProducts\Domain\Model\Officetip + */ + protected $officetip; + + /** + * @var \EP\EpProducts\Domain\Model\Snowreport + */ + protected $snowreport; + + protected function initStorageObjects() + { + parent::initStorageObjects(); + $this->facts = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return string + */ + public function getCode() { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) { + $this->code = $code; + } + + /** + * @return string + */ + public function getDescriptionExtended() + { + return $this->descriptionExtended; + } + + /** + * @param string $descriptionExtended + */ + public function setDescriptionExtended($descriptionExtended) + { + $this->descriptionExtended = $descriptionExtended; + } + + /** + * @param \EP\EpProducts\Domain\Model\Fact $fact + */ + public function addFact(\EP\EpProducts\Domain\Model\Fact $fact) + { + $this->facts->attach($fact); + } + + /** + * @param \EP\EpProducts\Domain\Model\Fact $factToRemove + */ + public function removeFact(\EP\EpProducts\Domain\Model\Fact $factToRemove) + { + $this->facts->detach($factToRemove); + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> + */ + public function getFacts() + { + return $this->facts; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function setFacts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $facts) + { + $this->facts = $facts; + } + + /** + * @return Officetip + */ + public function getOfficetip() + { + return $this->officetip; + } + + /** + * @param Officetip $officetip + */ + public function setOfficetip($officetip) + { + $this->officetip = $officetip; + } + + /** + * @return Snowreport + */ + public function getSnowreport() + { + return $this->snowreport; + } + + /** + * @param Snowreport $snowreport + */ + public function setSnowreport($snowreport) + { + $this->snowreport = $snowreport; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Date.php b/packages/ep_products/Classes/Domain/Model/Date.php new file mode 100644 index 0000000..c7b38eb --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Date.php @@ -0,0 +1,639 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Date extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + /** + * @var Product + */ + protected $product; + + /** + * @var Hotel + */ + protected $hotel; + + /** + * @var Country + */ + protected $country; + + /** + * @var Region + */ + protected $region; + + /** + * @var City + */ + protected $city; + + /** + * @var int + */ + protected $busProId = 0; + + /** + * @var int + */ + protected $hotelBusProId = 0; + + /** + * @var string + */ + protected $code = ''; + + /** + * @var bool + */ + protected $daytrip = false; + + /** + * @var \DateTime + */ + protected $dateStart; + + /** + * @var \DateTime + */ + protected $dateEnd; + + /** + * @var int + */ + protected $nights = 0; + + /** + * @var string + */ + protected $board = ''; + + /** + * @var string + */ + protected $boardCode = ''; + + /** + * @var int + */ + protected $boardBusProId = 0; + + /** + * @var bool + */ + protected $busIncluded = false; + + /** + * @var bool + */ + protected $skipassIncluded = false; + + /** + * @var bool + */ + protected $groupTravel = false; + + /** + * @var bool + */ + protected $busAvailable = false; + + /** + * @var float + */ + protected $busPrice = 0.0; + + /** + * @var string + */ + protected $servicesIncluded = ''; + + /** + * @var string + */ + protected $servicesGeneral = ''; + + /** + * @var string + */ + protected $coursesIncluded = ''; + + /** + * @var string + */ + protected $pickups = ''; + + /** + * @var float + */ + protected $discount = 0.0; + + /** + * @var float + */ + protected $minPrice = 0.0; + + /** + * @var float + */ + protected $pseudoPrice = 0.0; + + /** + * @var int + */ + protected $available = 0; + + /** + * @var int + */ + protected $minPax = 0; + + /** + * @var bool + */ + protected $new = false; + + /** + * @var string + */ + protected $guide = ''; + + public function getProduct() + { + return $this->product; + } + + public function setProduct($product) + { + $this->product = $product; + } + + public function getHotel() + { + return $this->hotel; + } + + public function setHotel($hotel) + { + $this->hotel = $hotel; + } + + public function getCountry() + { + return $this->country; + } + + public function setCountry($country) + { + $this->country = $country; + } + + public function getRegion() + { + return $this->region; + } + + public function setRegion($region) + { + $this->region = $region; + } + + public function getCity() + { + return $this->city; + } + + public function setCity($city) + { + $this->city = $city; + } + + /** + * @return int $bookingId + */ + public function getBusProId() + { + return $this->busProId; + } + + /** + * @param int $busProId + */ + public function setBusProId($busProId) + { + $this->busProId = $busProId; + } + + /** + * @return int + */ + public function getHotelBusProId() + { + return $this->hotelBusProId; + } + + /** + * @param int $hotelBusProId + */ + public function setHotelBusProId($hotelBusProId) + { + $this->hotelBusProId = $hotelBusProId; + } + + /** + * @return string $code + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @return bool + */ + public function isDaytrip() + { + return $this->daytrip; + } + + /** + * @param bool $daytrip + */ + public function setDaytrip($daytrip) + { + $this->daytrip = $daytrip; + } + + /** + * @return \DateTime $dateStart + */ + public function getDateStart() + { + return $this->dateStart; + } + + /** + * @param \DateTime $dateStart + */ + public function setDateStart(\DateTime $dateStart) + { + $this->dateStart = $dateStart; + } + + /** + * @return \DateTime $dateEnd + */ + public function getDateEnd() + { + return $this->dateEnd; + } + + /** + * @param \DateTime $dateEnd + */ + public function setDateEnd(\DateTime $dateEnd) + { + $this->dateEnd = $dateEnd; + } + + /** + * @return int + */ + public function getNights() + { + return $this->nights; + } + + /** + * @param int $nights + */ + public function setNights($nights) + { + $this->nights = $nights; + } + + /** + * @return string + */ + public function getBoard() + { + return $this->board; + } + + /** + * @param string $board + */ + public function setBoard($board) + { + $this->board = $board; + } + + /** + * @return string + */ + public function getBoardCode() + { + return $this->boardCode; + } + + /** + * @param string $boardCode + */ + public function setBoardCode(string $boardCode) + { + $this->boardCode = $boardCode; + } + + /** + * @return int + */ + public function getBoardBusProId() + { + return $this->boardBusProId; + } + + /** + * @param int $boardBusProId + */ + public function setBoardBusProId(int $boardBusProId) + { + $this->boardBusProId = $boardBusProId; + } + + /** + * @return bool $busIncluded + */ + public function getBusIncluded() + { + return $this->busIncluded; + } + + /** + * @param bool $busIncluded + */ + public function setBusIncluded($busIncluded) + { + $this->busIncluded = $busIncluded; + } + + /** + * @return bool + */ + public function getSkipassIncluded() + { + return $this->skipassIncluded; + } + + /** + * @param bool $skipassIncluded + */ + public function setSkipassIncluded($skipassIncluded) + { + $this->skipassIncluded = $skipassIncluded; + } + + /** + * @return bool + */ + public function getGroupTravel() + { + return $this->groupTravel; + } + + /** + * @param bool $groupTravel + */ + public function setGroupTravel($groupTravel) + { + $this->groupTravel = $groupTravel; + } + + /** + * @return bool + */ + public function getBusAvailable() + { + return $this->busAvailable; + } + + /** + * @param bool $busAvailable + */ + public function setBusAvailable($busAvailable) + { + $this->busAvailable = $busAvailable; + } + + /** + * @return float + */ + public function getBusPrice() + { + return $this->busPrice; + } + + /** + * @param float $busPrice + */ + public function setBusPrice($busPrice) + { + $this->busPrice = $busPrice; + } + + /** + * @return array $servicesIncluded + */ + public function getServicesIncluded() + { + return unserialize($this->servicesIncluded); + } + + /** + * @param array $servicesIncluded + */ + public function setServicesIncluded(array $servicesIncluded) + { + $this->servicesIncluded = serialize($servicesIncluded); + } + + /** + * @return array $servicesGeneral + */ + public function getServicesGeneral($token = null) + { + $services = unserialize($this->servicesGeneral); + + if (null !== $token) { + return $services[$token] ?? []; + } + + return $services; + } + + /** + * @param array $servicesGeneral + */ + public function setServicesGeneral(array $servicesGeneral) + { + $this->servicesGeneral = serialize($servicesGeneral); + } + + public function getCoursesIncluded() + { + return unserialize($this->coursesIncluded); + } + + public function setCoursesIncluded($coursesIncluded) + { + $this->coursesIncluded = $coursesIncluded; + } + + public function getActiveCoursesForTravelinfo() + { + return $this->getServicesGeneral('KUR'); + } + + public function getGuide() + { + return unserialize($this->guide); + } + + public function setGuide($guide): Date + { + $this->guide = serialize($guide); + } + + /** + * @return string + */ + public function getPickups() + { + return unserialize($this->pickups); + } + + /** + * @param array $pickups + */ + public function setPickups(array $pickups) + { + $this->pickups = serialize($pickups); + } + + /** + * @return float + */ + public function getDiscount() { + return $this->discount; + } + + /** + * @param float $discount + */ + public function setDiscount($discount) { + $this->discount = $discount; + } + + /** + * @return float + */ + public function getMinPrice() + { + return $this->minPrice; + } + + /** + * @param float $minPrice + */ + public function setMinPrice($minPrice) + { + $this->minPrice = $minPrice; + } + + /** + * @return float + */ + public function getPseudoPrice() + { + return $this->pseudoPrice; + } + + /** + * @param float $pseudoPrice + */ + public function setPseudoPrice($pseudoPrice) + { + $this->pseudoPrice = $pseudoPrice; + } + + /** + * @return int + */ + public function getAvailable() + { + return $this->available; + } + + /** + * @param int $available + */ + public function setAvailable($available) + { + $this->available = $available; + } + + public function getMinPax() + { + return $this->minPax; + } + + public function setMinPax($minPax) + { + $this->minPax = $minPax; + + return $this; + } + + public function isNew() + { + return $this->new; + } + + public function setNew(bool $new) + { + $this->new = $new; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Dto/FilterOptions.php b/packages/ep_products/Classes/Domain/Model/Dto/FilterOptions.php new file mode 100644 index 0000000..1b95aba --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Dto/FilterOptions.php @@ -0,0 +1,347 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class FilterOptions implements \JsonSerializable +{ + /** + * @var array + */ + public static $priceRangeOptions = [ + 1 => [ 1, 200 ], + 2 => [ 200, 300 ], + 3 => [ 300, 400 ], + 4 => [ 400, 500 ], + 5 => [ 500, 600 ], + 6 => [ 600, 999 ], + ]; + + /** + * @var array + */ + protected $priceRanges = []; + + /** + * @var array + */ + protected $destinations = []; + + /** + * @var array + */ + protected $concepts = []; + + /** + * @var array + */ + protected $pax = []; + + /** + * @var array + */ + protected $nights = []; + + /** + * @var array + */ + protected $hotelTypes = []; + + /** + * @var array + */ + protected $boardTypes = []; + + /** + * @var array + */ + protected $roomTypes = []; + + /** + * @var \DateTime + */ + protected $dateFrom; + + /** + * @var \DateTime + */ + protected $dateTo; + + /** + * @var bool + */ + protected $busAvailable; + + /** + * @var int + */ + protected $resultCount; + + /** + * @var string + */ + protected $resultsUri; + + public function __construct() + { + $this->pax = [ + 0 => 'beliebig', + ...array_combine(range(1, 24), range(1, 24)) + ]; + } + + /** + * @return array + */ + public function getPriceRanges() + { + return $this->priceRanges; + } + + /** + * @param array $priceRanges + */ + public function setPriceRanges($priceRanges) + { + $this->priceRanges = $priceRanges; + } + + /** + * @return array + */ + public function getDestinations() + { + return $this->destinations; + } + + /** + * @param array $destinations + */ + public function setDestinations(array $destinations) + { + $this->destinations = $destinations; + } + + /** + * @return array + */ + public function getConcepts() + { + return $this->concepts; + } + + /** + * @param array $concepts + */ + public function setConcepts($concepts) + { + $this->concepts = $concepts; + } + + /** + * @return array + */ + public function getPax() + { + return $this->pax; + } + + /** + * @param array $pax + */ + public function setPax($pax) + { + $this->pax = $pax; + } + + /** + * @return array + */ + public function getNights() + { + return $this->nights; + } + + /** + * @param array $nights + */ + public function setNights($nights) + { + $this->nights = $nights; + } + + /** + * @return array + */ + public function getHotelTypes() + { + return $this->hotelTypes; + } + + /** + * @param array $hotelTypes + */ + public function setHotelTypes($hotelTypes) + { + $this->hotelTypes = $hotelTypes; + } + + /** + * @return array + */ + public function getBoardTypes() + { + return $this->boardTypes; + } + + /** + * @param array $boardTypes + */ + public function setBoardTypes($boardTypes) + { + $this->boardTypes = $boardTypes; + } + + /** + * @return array + */ + public function getRoomTypes() + { + return $this->roomTypes; + } + + /** + * @param array $roomTypes + */ + public function setRoomTypes($roomTypes) + { + $this->roomTypes = $roomTypes; + } + + /** + * @return \DateTime + */ + public function getDateFrom() + { + return $this->dateFrom; + } + + /** + * @param \DateTime $dateFrom + */ + public function setDateFrom($dateFrom) + { + $this->dateFrom = $dateFrom; + } + + /** + * @return \DateTime + */ + public function getDateTo() + { + return $this->dateTo; + } + + /** + * @param \DateTime $dateTo + */ + public function setDateTo($dateTo) + { + $this->dateTo = $dateTo; + } + + /** + * @return bool + */ + public function getBusAvailable() + { + return $this->busAvailable; + } + + /** + * @param bool $busAvailable + */ + public function setBusAvailable($busAvailable) + { + $this->busAvailable = $busAvailable; + } + + /** + * @return int + */ + public function getResultCount(): ?int + { + return $this->resultCount; + } + + /** + * @param int $resultCount + */ + public function setResultCount(int $resultCount) + { + $this->resultCount = $resultCount; + } + + /** + * @return string + */ + public function getResultsUri(): ?string + { + return $this->resultsUri; + } + + /** + * @param string $resultsUri + */ + public function setResultsUri(string $resultsUri) + { + $this->resultsUri = $resultsUri; + } + + /** + * @return array + */ + public function jsonSerialize() + { + return [ + 'priceRanges' => $this->getPriceRanges(), + 'destinations' => $this->getDestinations(), + 'concepts' => $this->getConcepts(), + 'pax' => $this->getPax(), + 'nights' => $this->getNights(), + 'hotelTypes' => $this->getHotelTypes(), + 'boardTypes' => $this->getBoardTypes(), + 'roomTypes' => $this->getRoomTypes(), + 'dateFrom' => ($this->getDateFrom() !== null) ? $this->getDateFrom()->format('Y-m-d') : null, + 'dateTo' => ($this->getDateTo() !== null) ? $this->getDateTo()->format('Y-m-d') : null, + 'busAvailable' => $this->getBusAvailable(), + 'resultCount' => $this->getResultCount(), + 'resultsUri' => $this->getResultsUri(), + ]; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php b/packages/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php new file mode 100644 index 0000000..0a9d3ad --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Dto/GroupsPriceInquiry.php @@ -0,0 +1,484 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class GroupsPriceInquiry +{ + const MODE_INQUIRY = 'inquiry'; + const MODE_BOOKING = 'booking'; + + /** + * @var string + */ + protected $mode; + + /** + * @var bool + * @TYPO3\CMS\Extbase\Annotation\Validate("Boolean", options={"is": true}) + */ + protected $confirmation; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $name; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $group; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $street; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $postcode; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $city; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + * @TYPO3\CMS\Extbase\Annotation\Validate("EmailAddress") + */ + protected $email; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $phone; + + /** + * @var string + */ + protected $remarks; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $dateFrom; + + /** + * @var string + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $dateTo; + + /** + * @var string + */ + protected $nights; + + /** + * @var string + */ + protected $pax; + + /** + * @var string + */ + protected $children; + + /** + * @var string + */ + protected $minors; + + /** + * @var string + */ + protected $adolescents; + + /** + * @var array + */ + protected $options = []; + + /** + * @var GroupsPriceBoard + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + protected $board; + + /** + * @var array + */ + protected $summary; + + public function __construct() + { + $this->mode = self::MODE_BOOKING; + } + + /** + * @return bool + */ + public function isConfirmation() + { + return $this->confirmation; + } + + /** + * @param bool $confirmation + */ + public function setConfirmation($confirmation) + { + $this->confirmation = $confirmation; + } + + /** + * @return string + */ + public function getMode() + { + return $this->mode; + } + + /** + * @param string $mode + */ + public function setMode($mode) + { + $this->mode = $mode; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getGroup() + { + return $this->group; + } + + /** + * @param string $group + */ + public function setGroup($group) + { + $this->group = $group; + + return $this; + } + + /** + * @return string + */ + public function getStreet() + { + return $this->street; + } + + /** + * @param string $street + */ + public function setStreet($street) + { + $this->street = $street; + + return $this; + } + + /** + * @return string + */ + public function getPostcode() + { + return $this->postcode; + } + + /** + * @param string $postcode + */ + public function setPostcode($postcode) + { + $this->postcode = $postcode; + } + + /** + * @return string + */ + public function getCity() + { + return $this->city; + } + + /** + * @param string $city + */ + public function setCity($city) + { + $this->city = $city; + } + + /** + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * @param string $email + */ + public function setEmail($email) + { + $this->email = $email; + } + + /** + * @return string + */ + public function getPhone() + { + return $this->phone; + } + + /** + * @param string $phone + */ + public function setPhone($phone) + { + $this->phone = $phone; + } + + /** + * @return string + */ + public function getRemarks() + { + return $this->remarks; + } + + /** + * @param string $remarks + */ + public function setRemarks($remarks) + { + $this->remarks = $remarks; + } + + /** + * @return string + */ + public function getDateFrom() + { + return $this->dateFrom; + } + + /** + * @param string $dateFrom + */ + public function setDateFrom($dateFrom) + { + $this->dateFrom = $dateFrom; + } + + /** + * @return string + */ + public function getDateTo() + { + return $this->dateTo; + } + + /** + * @param string $dateTo + */ + public function setDateTo($dateTo) + { + $this->dateTo = $dateTo; + } + + /** + * @return string + */ + public function getNights() + { + return $this->nights; + } + + /** + * @param string $nights + */ + public function setNights($nights) + { + $this->nights = $nights; + } + + /** + * @return string + */ + public function getPax() + { + return $this->pax; + } + + /** + * @param string $pax + */ + public function setPax($pax) + { + $this->pax = $pax; + } + + /** + * @return string + */ + public function getChildren() + { + return $this->children; + } + + /** + * @param string $pax + */ + public function setChildren($children) + { + $this->children = $children; + } + + /** + * @return string + */ + public function getMinors() + { + return $this->minors; + } + + /** + * @param string $minors + */ + public function setMinors($minors) + { + $this->minors = $minors; + } + + /** + * @return string + */ + public function getAdolescents() + { + return $this->adolescents; + } + + /** + * @param string $adolescents + */ + public function setAdolescents($adolescents) + { + $this->adolescents = $adolescents; + } + + /** + * @return int + */ + public function getEffectivePax() + { + return max(30, (int) $this->getPax() - (int) $this->getChildren()); + } + + /** + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * @param array $options + */ + public function setOptions(array $options = []) + { + $this->options = $options; + } + + /** + * @return GroupsPriceBoard + */ + public function getBoard() + { + return $this->board; + } + + /** + * @param GroupsPriceBoard $board + */ + public function setBoard(GroupsPriceBoard $board = null) + { + $this->board = $board; + } + + /** + * @return array + */ + public function getSummary() + { + return $this->summary; + } + + /** + * @param array $summary + */ + public function setSummary(array $summary) + { + $this->summary = $summary; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php b/packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php new file mode 100644 index 0000000..2a976ce --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php @@ -0,0 +1,113 @@ + + * @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty") + */ + private array $listIds = []; + + /** + * @TYPO3\CMS\Extbase\Annotation\Validate("Boolean", options={"is": true}) + */ + private bool $consent = false; + + public function getFirstName(): ?string + { + return $this->firstName; + } + + public function setFirstName(?string $firstName): self + { + $this->firstName = $firstName; + + return $this; + } + + public function getLastName(): ?string + { + return $this->lastName; + } + + public function setLastName(?string $lastName): self + { + $this->lastName = $lastName; + + return $this; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(?string $email): self + { + $this->email = $email; + + return $this; + } + + /** + * @return list + */ + public function getListIds(): array + { + return $this->listIds; + } + + /** + * @param list $listIds + */ + public function setListIds(array $listIds): self + { + $this->listIds = $listIds; + + return $this; + } + + public function isConsent(): bool + { + return $this->consent; + } + + public function setConsent(bool $consent): self + { + $this->consent = $consent; + + return $this; + } + + public function jsonSerialize(): array + { + return [ + 'firstName' => $this->firstName, + 'lastName' => $this->lastName, + 'email' => $this->email, + 'listIds' => array_map(function($value) { + return (int) $value; + }, $this->listIds), + ]; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Fact.php b/packages/ep_products/Classes/Domain/Model/Fact.php new file mode 100644 index 0000000..79207cd --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Fact.php @@ -0,0 +1,96 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Fact extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $keywords = ''; + + /** + * @var string + */ + protected $label = ''; + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string $keywords + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * @param string $keywords + */ + public function setKeywords($keywords) + { + $this->keywords = $keywords; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * @param string $label + */ + public function setLabel($label) + { + $this->label = $label; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Faq.php b/packages/ep_products/Classes/Domain/Model/Faq.php new file mode 100644 index 0000000..ab8bd89 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Faq.php @@ -0,0 +1,75 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Faq extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $question = ''; + + /** + * @var string + */ + protected $answer = ''; + + /** + * @return string $question + */ + public function getQuestion() + { + return $this->question; + } + + /** + * @param string $question + */ + public function setQuestion($question) + { + $this->question = $question; + } + + /** + * @return string $answer + */ + public function getAnswer() + { + return $this->answer; + } + + /** + * @param string $answer + */ + public function setAnswer($answer) + { + $this->answer = $answer; + } + +} \ No newline at end of file diff --git a/packages/ep_products/Classes/Domain/Model/GroupsPriceBoard.php b/packages/ep_products/Classes/Domain/Model/GroupsPriceBoard.php new file mode 100644 index 0000000..f05e0bb --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/GroupsPriceBoard.php @@ -0,0 +1,111 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class GroupsPriceBoard extends AbstractEntity implements \JsonSerializable +{ + /** + * @var string + */ + protected $title; + + /** + * @var float + */ + protected $price; + + /** + * @var float + */ + protected $priceChf; + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return float + */ + public function getPrice(): float + { + return $this->price; + } + + /** + * @param float $price + */ + public function setPrice(float $price): void + { + $this->price = $price; + } + + /** + * @return float + */ + public function getPriceChf(): ?float + { + return $this->priceChf; + } + + /** + * @param float $price + */ + public function setPriceChf(float $price): void + { + $this->priceChf = $price; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'uid' => $this->getUid(), + 'title' => $this->getTitle(), + 'price' => [ + 'EUR' => $this->getPrice(), + 'CHF' => $this->getPriceChf(), + ], + ]; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/GroupsPriceConfig.php b/packages/ep_products/Classes/Domain/Model/GroupsPriceConfig.php new file mode 100644 index 0000000..dc8a8d0 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/GroupsPriceConfig.php @@ -0,0 +1,201 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class GroupsPriceConfig extends AbstractEntity implements \JsonSerializable +{ + /** + * @var \DateTime + */ + protected $dateFrom; + + /** + * @var \DateTime + */ + protected $dateTo; + + /** + * @var int + */ + protected $personsIncluded; + + /** + * @var float + */ + protected $price; + + /** + * @var float + */ + protected $priceChf; + + /** + * @var float + */ + protected $priceAdditionalPerson; + + /** + * @var float + */ + protected $priceAdditionalPersonChf; + + /** + * @return \DateTime + */ + public function getDateFrom(): \DateTime + { + return $this->dateFrom; + } + + /** + * @param \DateTime $dateFrom + */ + public function setDateFrom(\DateTime $dateFrom): void + { + $this->dateFrom = $dateFrom; + } + + /** + * @return \DateTime + */ + public function getDateTo(): \DateTime + { + return $this->dateTo; + } + + /** + * @param \DateTime $dateTo + */ + public function setDateTo(\DateTime $dateTo): void + { + $this->dateTo = $dateTo; + } + + /** + * @return int + */ + public function getPersonsIncluded(): int + { + return $this->personsIncluded; + } + + /** + * @param int $personsIncluded + */ + public function setPersonsIncluded(int $personsIncluded): void + { + $this->personsIncluded = $personsIncluded; + } + + /** + * @return float + */ + public function getPrice(): float + { + return $this->price; + } + + /** + * @param float $price + */ + public function setPrice(float $price): void + { + $this->price = $price; + } + + /** + * @return float + */ + public function getPriceChf(): ?float + { + return $this->priceChf; + } + + /** + * @param float $price + */ + public function setPriceChf(float $price): void + { + $this->priceChf = $price; + } + + /** + * @return float + */ + public function getPriceAdditionalPerson(): ?float + { + return $this->priceAdditionalPerson; + } + + /** + * @param float $priceAdditionalPerson + */ + public function setPriceAdditionalPerson(float $priceAdditionalPerson): void + { + $this->priceAdditionalPerson = $priceAdditionalPerson; + } + + /** + * @return float + */ + public function getPriceAdditionalPersonChf(): ?float + { + return $this->priceAdditionalPersonChf; + } + + /** + * @param float $priceAdditionalPerson + */ + public function setPriceAdditionalPersonChf(float $priceAdditionalPerson): void + { + $this->priceAdditionalPersonChf = $priceAdditionalPerson; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'uid' => $this->getUid(), + 'dateFrom' => $this->getDateFrom()->format('Y-m-d'), + 'dateTo' => $this->getDateTo()->format('Y-m-d'), + 'personsIncluded' => $this->getPersonsIncluded(), + 'price' => [ + 'EUR' => $this->getPrice(), + 'CHF' => $this->getPriceChf(), + ], + 'priceAdditionalPerson' => [ + 'EUR' => $this->getPriceAdditionalPerson(), + 'CHF' => $this->getPriceAdditionalPersonChf(), + ], + ]; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/GroupsPriceOption.php b/packages/ep_products/Classes/Domain/Model/GroupsPriceOption.php new file mode 100644 index 0000000..d61da7d --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/GroupsPriceOption.php @@ -0,0 +1,160 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class GroupsPriceOption extends AbstractEntity implements \JsonSerializable +{ + const TYPE_GLOBAL = 1; + const TYPE_PER_PAX = 2; + const TYPE_PER_NIGHT = 3; + const TYPE_PER_PAX_AND_NIGHT = 4; + + /** + * @var string + */ + protected $title; + + /** + * @var float + */ + protected $price; + + /** + * @var float + */ + protected $priceChf; + + /** + * @var int + */ + protected $type; + + /** + * @var bool + */ + protected $ignoreWithBoard = false; + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return float + */ + public function getPrice(): float + { + return $this->price; + } + + /** + * @param float $price + */ + public function setPrice(float $price): void + { + $this->price = $price; + } + + /** + * @return float + */ + public function getPriceChf(): ?float + { + return $this->priceChf; + } + + /** + * @param float $price + */ + public function setPriceChf(float $price): void + { + $this->priceChf = $price; + } + + /** + * @return int + */ + public function getType(): int + { + return $this->type; + } + + /** + * @param int $type + */ + public function setType(int $type): void + { + $this->type = $type; + } + + /** + * @return bool + */ + public function isIgnoreWithBoard(): bool + { + return $this->ignoreWithBoard; + } + + /** + * @param bool $ignoreWithBoard + */ + public function setIgnoreWithBoard(bool $ignoreWithBoard): void + { + $this->ignoreWithBoard = $ignoreWithBoard; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'uid' => $this->getUid(), + 'title' => $this->getTitle(), + 'price' => [ + 'EUR' => $this->getPrice(), + 'CHF' => $this->getPriceChf(), + ], + 'type' => $this->getType(), + 'ignoreWithBoard' => $this->isIgnoreWithBoard(), + ]; + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Hotel.php b/packages/ep_products/Classes/Domain/Model/Hotel.php new file mode 100644 index 0000000..097fbe8 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Hotel.php @@ -0,0 +1,1273 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Utility\HotelIconUtility; +use TYPO3\CMS\Extbase\Domain\Model\FileReference; +use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; +use TYPO3\CMS\Extbase\Persistence\ObjectStorage; + +class Hotel extends AbstractEntity implements TeaserInterface +{ + + const CATEGORY_STANDARD = 1; + const CATEGORY_COMFORT = 2; + const CATEGORY_DELUXE = 3; + const CATEGORY_SUPER_DELUXE = 4; + const CATEGORY_BASIC = 5; + + const TYPE_HOLIDAY_FLAT = 1; + const TYPE_SPORTS_CLUB = 2; + const TYPE_APPARTMENT = 3; + const TYPE_GUESTHOUSE = 4; + const TYPE_PENSION = 5; + + /** + * @var array + */ + public static $categoryLabels = [ + self::CATEGORY_BASIC => 'Basic', + self::CATEGORY_STANDARD => 'Standard', + self::CATEGORY_COMFORT => 'Komfort', + self::CATEGORY_DELUXE => 'Deluxe', + self::CATEGORY_SUPER_DELUXE => 'Superdeluxe', + ]; + + /** + * @var array + */ + public static $typeLabels = [ + 0 => 'Unbestimmt', + self::TYPE_HOLIDAY_FLAT => 'Ferienwohnung', + self::TYPE_SPORTS_CLUB => 'Sportclub', + self::TYPE_APPARTMENT => 'Apartment', + self::TYPE_GUESTHOUSE => 'Gästehaus', + self::TYPE_PENSION => 'Pension', + ]; + + /** + * @var bool + */ + protected $hidden = true; + + /** + * @var bool + */ + protected $earlybird = false; + + /** + * @var bool + */ + protected $new = false; + + /** + * @var bool + */ + protected $lowContingent = false; + + /** + * @var bool + */ + protected $showAsTeaser = false; + + /** + * @var string + */ + protected $detailPage; + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $shortName; + + /** + * @var string + */ + protected $keywords = ''; + + /** + * @var string + */ + protected $headline = ''; + + /** + * @var string + */ + protected $teaser = ''; + + /** + * @var string + */ + protected $headerTitle = ''; + + /** + * @var string + */ + protected $headerSubtitle = ''; + + /** + * @var string + */ + protected $description = ''; + + /** + * @var string + */ + protected $features = ''; + + /** + * @var string + */ + protected $roomTypes = ''; + + /** + * @var string + */ + protected $additionalInformation = ''; + + /** + * @var float + */ + protected $latitude = 0.0; + + /** + * @var float + */ + protected $longitude = 0.0; + + /** + * @var string + */ + protected $address = ''; + + /** + * @var ObjectStorage + */ + protected $images; + + /** + * @var ObjectStorage + */ + protected $resellerImages; + + /** + * @var ObjectStorage + */ + protected $teaserImages; + + /** + * @var ObjectStorage + */ + protected $headerImages; + + /** + * @var string + */ + protected $code = ''; + + /** + * @var ObjectStorage + * @TYPO3\CMS\Extbase\Annotation\ORM\Lazy + */ + protected $additionalCodes; + + /** + * @var int + */ + protected $type = 0; + + /** + * @var int + */ + protected $category = self::CATEGORY_STANDARD; + + /** + * @var Country + */ + protected $country; + + /** + * @var Region + */ + protected $region; + + /** + * @var City + */ + protected $city; + + /** + * @var ObjectStorage + */ + protected $teamer; + + /** + * @var ObjectStorage + */ + protected $products; + + /** + * @var ObjectStorage + */ + protected $facts; + + /** + * @var string + */ + protected $externalLink; + + /** + * @var string + */ + protected $pathSegment; + + /** + * @var ObjectStorage + */ + protected $groupsPriceConfigs; + + /** + * @var ObjectStorage + */ + protected $groupsPriceOptions; + + /** + * @var ObjectStorage + */ + protected $groupsPriceBoards; + + /** + * @var int + */ + protected $groupsPriceAdolescentsAge; + + /** + * @var string + */ + protected $groupsPriceTaxLabel; + + /** + * @var \DateTime + */ + protected $mainSeasonFrom; + + /** + * @var \DateTime + */ + protected $mainSeasonTo; + + /** + * @var int + */ + protected $bedsCount = 0; + + /** + * @var int + */ + protected $parkingCount = 0; + + /** + * @var string + */ + protected $altitude = ''; + + /** + * @var string + */ + protected $shower = ''; + + /** + * @var bool + */ + protected $partyroom = false; + + /** + * @var bool + */ + protected $gamesroom = false; + + /** + * @var bool + */ + protected $sauna = false; + + /** + * @var bool + */ + protected $wifi = false; + + /** + * @var string + */ + protected $distanceBus = ''; + + /** + * @var string + */ + protected $distanceLift = ''; + + public function __construct() + { + $this->initStorageObjects(); + } + + protected function initStorageObjects() + { + $this->images = new ObjectStorage(); + $this->resellerImages = new ObjectStorage(); + $this->teaserImages = new ObjectStorage(); + $this->headerImages = new ObjectStorage(); + $this->teamer = new ObjectStorage(); + $this->products = new ObjectStorage(); + $this->facts = new ObjectStorage(); + $this->additionalCodes = new ObjectStorage(); + $this->groupsPriceConfigs = new ObjectStorage(); + $this->groupsPriceOptions = new ObjectStorage(); + $this->groupsPriceBoards = new ObjectStorage(); + } + + /** + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * @param bool $hidden + */ + public function setHidden($hidden) + { + $this->hidden = $hidden; + } + + /** + * @return bool + */ + public function isEarlybird() + { + return $this->earlybird; + } + + /** + * @param bool $earlybird + */ + public function setEarlybird($earlybird) + { + $this->earlybird = $earlybird; + } + + /** + * @return bool + */ + public function isNew() + { + return $this->new; + } + + /** + * @param bool $new + */ + public function setNew($new) + { + $this->new = $new; + } + + /** + * @return bool + */ + public function isLowContingent() + { + return $this->lowContingent; + } + + /** + * @param bool $lowContingent + */ + public function setLowContingent($lowContingent) + { + $this->lowContingent = $lowContingent; + } + + /** + * @return bool + */ + public function isShowAsTeaser() + { + return $this->showAsTeaser; + } + + /** + * @param bool $showAsTeaser + */ + public function setShowAsTeaser($showAsTeaser) + { + $this->showAsTeaser = $showAsTeaser; + } + + /** + * @return string + */ + public function getDetailPage() + { + return $this->detailPage; + } + + /** + * @param string $detailPage + */ + public function setDetailPage($detailPage) + { + $this->detailPage = $detailPage; + } + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getShortName() + { + if (empty($this->shortName)) { + return $this->name; + } + + return $this->shortName; + } + + /** + * @param string $shortName + */ + public function setShortName($shortName) + { + $this->shortName = $shortName; + } + + /** + * @return string + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * @param string $keywords + */ + public function setKeywords($keywords) + { + $this->keywords = $keywords; + } + + /** + * @return string + */ + public function getHeadline() { + return $this->headline; + } + + /** + * @param string $headline + */ + public function setHeadline($headline) { + $this->headline = $headline; + } + + /** + * @return string + */ + public function getTeaser() { + return $this->teaser; + } + + /** + * @param string $teaser + */ + public function setTeaser($teaser) { + $this->teaser = $teaser; + } + + /** + * @return string + */ + public function getHeaderTitle() + { + return $this->headerTitle; + } + + /** + * @param string $headerTitle + */ + public function setHeaderTitle($headerTitle) + { + $this->headerTitle = $headerTitle; + } + + /** + * @return string + */ + public function getHeaderSubtitle() + { + return $this->headerSubtitle; + } + + /** + * @param string $headerSubtitle + */ + public function setHeaderSubtitle($headerSubtitle) + { + $this->headerSubtitle = $headerSubtitle; + } + + /** + * @return string $description + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return string + */ + public function getFeatures() { + return $this->features; + } + + /** + * @param string $features + */ + public function setFeatures($features) { + $this->features = $features; + } + + /** + * @return string + */ + public function getRoomTypes() { + return $this->roomTypes; + } + + /** + * @param string $roomTypes + */ + public function setRoomTypes($roomTypes) { + $this->roomTypes = $roomTypes; + } + + /** + * @return string + */ + public function getAdditionalInformation() { + return $this->additionalInformation; + } + + /** + * @param string $additionalInformation + */ + public function setAdditionalInformation($additionalInformation) { + $this->additionalInformation = $additionalInformation; + } + + /** + * @return float + */ + public function getLatitude() + { + if (empty($this->latitude) && $this->region !== null) { + return $this->region->getLatitude(); + } + + return $this->latitude; + } + + /** + * @param float $latitude + */ + public function setLatitude($latitude) + { + $this->latitude = $latitude; + } + + /** + * @return float + */ + public function getLongitude() + { + if (empty($this->longitude) && $this->region !== null) { + return $this->region->getLongitude(); + } + + return $this->longitude; + } + + /** + * @param float $longitude + */ + public function setLongitude($longitude) + { + $this->longitude = $longitude; + } + + /** + * @return string + */ + public function getAddress() + { + return $this->address; + } + + /** + * @param string $address + */ + public function setAddress($address) + { + $this->address = $address; + } + + /** + * @return ObjectStorage $images + */ + public function getImages() + { + return $this->images; + } + + /** + * @param ObjectStorage $images + */ + public function setImages(ObjectStorage $images) + { + $this->images = $images; + } + + /** + * @return ObjectStorage $resellerImages + */ + public function getResellerImages() + { + return $this->resellerImages; + } + + /** + * @param ObjectStorage $resellerImages + */ + public function setResellerImages($resellerImages) + { + $this->resellerImages = $resellerImages; + } + + /** + * @return ObjectStorage + */ + public function getTeaserImages() + { + return $this->teaserImages; + } + + /** + * @return null|ObjectStorage + */ + public function getTeaserImage() + { + if ($this->teaserImages->count() === 0) { + return null; + } + + $images = $this->teaserImages->toArray(); + + return reset($images); + } + + /** + * @param ObjectStorage $teaserImages + */ + public function setTeaserImages($teaserImages) + { + $this->teaserImages = $teaserImages; + } + + /** + * @return ObjectStorage + */ + public function getHeaderImages() + { + $headerImages = $this->headerImages; + + if ($headerImages->count() === 0 && null !== $this->getCity()) { + $headerImages = $this->getCity()->getHeaderImages(); + } + + return $headerImages; + } + + /** + * @return null|ObjectStorage + */ + public function getHeaderImage() + { + if ($this->headerImages->count() === 0) { + return null; + } + + $images = $this->headerImages->toArray(); + + return reset($images); + } + + /** + * @param ObjectStorage $headerImages + */ + public function setHeaderImages($headerImages) + { + $this->headerImages = $headerImages; + } + + /** + * @return string $code + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @param MatchCode $code + */ + public function addAdditionalCode(MatchCode $code) + { + $this->additionalCodes->attach($code); + } + + /** + * @param MatchCode $codeToRemove + */ + public function removeAdditionalCodes(MatchCode $codeToRemove) + { + $this->additionalCodes->detach($codeToRemove); + } + + /** + * @return ObjectStorage $codes + */ + public function getAdditionalCodes() + { + return $this->additionalCodes; + } + + /** + * @param ObjectStorage $codes + */ + public function setAdditionalCodes(ObjectStorage $codes) + { + $this->additionalCodes = $codes; + } + + /** + * @return int $type + */ + public function getType() + { + return $this->type; + } + + /** + * @param int $type + */ + public function setType($type) + { + $this->type = $type; + } + + /** + * @return int $comfort + */ + public function getCategory() + { + return $this->category; + } + + /** + * @return string + */ + public function getCategoryLabel() + { + if ($category = $this->getCategory()) { + return static::$categoryLabels[$category]; + } + + return null; + } + + /** + * @param int $category + */ + public function setCategory($category) + { + $this->category = $category; + } + + /** + * @return Country $country + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param Country $country + */ + public function setCountry(Country $country) + { + $this->country = $country; + } + + /** + * @return Region $region + */ + public function getRegion() + { + return $this->region; + } + + /** + * @param Region $region + */ + public function setRegion(Region $region) + { + $this->region = $region; + } + + /** + * @return City $city + */ + public function getCity() + { + return $this->city; + } + + /** + * @param City $city + */ + public function setCity(City $city) + { + $this->city = $city; + } + + /** + * @param Teammember $teamer + */ + public function addTeamer(Teammember $teamer) + { + $this->teamer->attach($teamer); + } + + /** + * @param Teammember $teamerToRemove The Teammember to be removed + */ + public function removeTeamer(Teammember $teamerToRemove) + { + $this->teamer->detach($teamerToRemove); + } + + /** + * @return ObjectStorage $teamer + */ + public function getTeamer() + { + return $this->teamer; + } + + /** + * @param ObjectStorage $teamer + */ + public function setTeamer(ObjectStorage $teamer) + { + $this->teamer = $teamer; + } + + /** + * @param Product $product + */ + public function addProduct(Product $product) + { + $this->products->attach($product); + } + + /** + * @param Product $product + */ + public function removeProduct(Product $product) + { + $this->products->detach($product); + } + + /** + * @return ObjectStorage + */ + public function getProducts() + { + return $this->products; + } + + /** + * @param ObjectStorage $products + */ + public function setProducts(ObjectStorage $products) + { + $this->products = $products; + } + + /** + * @param ObjectStorage $rooms + */ + public function setRooms($rooms) { + $this->rooms = $rooms; + } + + /** + * @param Fact $fact + */ + public function addFact(Fact $fact) + { + $this->facts->attach($fact); + } + + /** + * @param Fact $factToRemove + */ + public function removeFact(Fact $factToRemove) + { + $this->facts->detach($factToRemove); + } + + /** + * @return ObjectStorage + */ + public function getFacts() + { + return $this->facts; + } + + /** + * @param ObjectStorage $facts + */ + public function setFacts(ObjectStorage $facts) + { + $this->facts = $facts; + } + + /** + * @return string + */ + public function getExternalLink() + { + return $this->externalLink; + } + + /** + * @return string + */ + public function getPathSegment() + { + return $this->pathSegment; + } + + /** + * @param string $externalLink + */ + public function setExternalLink($externalLink) + { + $this->externalLink = $externalLink; + } + + /** + * @return bool + */ + public function getIsApartment() + { + return $this->getType() === self::TYPE_APPARTMENT; + } + + /** + * @return bool + */ + public function getIsHolidayFlat() + { + return $this->getType() === self::TYPE_HOLIDAY_FLAT; + } + + /** + * @return bool + */ + public function getIsOther() + { + return !$this->getIsApartment() && !$this->getIsHolidayFlat(); + } + + /** + * @return ObjectStorage + */ + public function getGroupsPriceConfigs() + { + return $this->groupsPriceConfigs; + } + + /** + * @param ObjectStorage $groupPriceConfigs + */ + public function setGroupsPriceConfigs(ObjectStorage $groupPriceConfigs) + { + $this->groupsPriceConfigs = $groupPriceConfigs; + } + + /** + * @return ObjectStorage + */ + public function getGroupsPriceOptions() + { + return $this->groupsPriceOptions; + } + + /** + * @param ObjectStorage $groupsPriceOptions + */ + public function setGroupsPriceOptions(ObjectStorage $groupsPriceOptions) + { + $this->groupsPriceOptions = $groupsPriceOptions; + } + + /** + * @return ObjectStorage + */ + public function getGroupsPriceBoards() + { + return $this->groupsPriceBoards; + } + + /** + * @param ObjectStorage $groupsPriceBoards + */ + public function setGroupsPriceBoards(ObjectStorage $groupsPriceBoards) + { + $this->groupsPriceBoards = $groupsPriceBoards; + } + + public function getGroupsPriceAdolescentsAge() + { + return $this->groupsPriceAdolescentsAge; + } + + public function setGroupsPriceAdolescentsAge($groupsPriceAdolescentsAge) + { + $this->groupsPriceAdolescentsAge = $groupsPriceAdolescentsAge; + } + + public function getGroupsPriceTaxLabel() + { + return $this->groupsPriceTaxLabel; + } + + public function setGroupsPriceTaxLabel($groupsPriceTaxLabel) + { + $this->groupsPriceTaxLabel = $groupsPriceTaxLabel; + } + + public function getMainSeasonFrom() + { + return $this->mainSeasonFrom; + } + + public function setMainSeasonFrom(\DateTime $mainSeasonFrom) + { + $this->mainSeasonFrom = $mainSeasonFrom; + } + + public function getMainSeasonTo() + { + return $this->mainSeasonTo; + } + + public function setMainSeasonTo(\DateTime $mainSeasonTo) + { + $this->mainSeasonTo = $mainSeasonTo; + } + + public function getBedsCount() + { + return $this->bedsCount; + } + + public function setBedsCount($bedsCount) + { + $this->bedsCount = $bedsCount; + } + + public function getParkingCount() + { + return $this->parkingCount; + } + + public function setParkingCount($parkingCount) + { + $this->parkingCount = $parkingCount; + } + + public function getAltitude() + { + return $this->altitude; + } + + public function setAltitude($altitude) + { + $this->altitude = $altitude; + } + + public function getShower() + { + return $this->shower; + } + + public function setShower($shower) + { + $this->shower = $shower; + } + + public function isPartyroom() + { + return $this->partyroom; + } + + public function setPartyroom($partyroom) + { + $this->partyroom = $partyroom; + } + + public function isGamesroom() + { + return $this->gamesroom; + } + + public function setGamesroom($gamesroom) + { + $this->gamesroom = $gamesroom; + } + + public function isSauna() + { + return $this->sauna; + } + + public function setSauna($sauna) + { + $this->sauna = $sauna; + } + + public function isWifi() + { + return $this->wifi; + } + + public function setWifi($wifi) + { + $this->wifi = $wifi; + } + + public function getDistanceBus() + { + return $this->distanceBus; + } + + public function setDistanceBus($distanceBus) + { + $this->distanceBus = $distanceBus; + } + + public function getDistanceLift() + { + return $this->distanceLift; + } + + public function setDistanceLift($distanceLift) + { + $this->distanceLift = $distanceLift; + } + + public function getIcons(): array + { + return HotelIconUtility::getIcons([ + 'beds_count' => $this->bedsCount, + 'parking_count' => $this->parkingCount, + 'altitude' => $this->altitude, + 'distance_bus' => $this->distanceBus, + 'distance_lift' => $this->distanceLift, + 'shower' => $this->shower, + 'partyroom' => $this->partyroom, + 'gamesroom' => $this->gamesroom, + 'sauna' => $this->sauna, + 'wifi' => $this->wifi, + ]); + } +} diff --git a/packages/ep_products/Classes/Domain/Model/Infobox.php b/packages/ep_products/Classes/Domain/Model/Infobox.php new file mode 100644 index 0000000..d7884b5 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Infobox.php @@ -0,0 +1,73 @@ +title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + /** + * @return bool + */ + public function isDefaultText() + { + return $this->defaultText; + } + + /** + * @param bool $defaultText + */ + public function setDefaultText($defaultText) + { + $this->defaultText = $defaultText; + } +} + diff --git a/packages/ep_products/Classes/Domain/Model/Journey.php b/packages/ep_products/Classes/Domain/Model/Journey.php new file mode 100644 index 0000000..cc54f3b --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Journey.php @@ -0,0 +1,117 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Journey extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $byCar = ''; + + /** + * @var string + */ + protected $byBus; + + /** + * @var \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + protected $image; + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getByCar() + { + return $this->byCar; + } + + /** + * @param string $byCar + */ + public function setByCar($byCar) + { + $this->byCar = $byCar; + } + + /** + * @return string + */ + public function getByBus() + { + return $this->byBus; + } + + /** + * @param string $byBus + */ + public function setByBus($byBus) + { + $this->byBus = $byBus; + } + + /** + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getImage() + { + return $this->image; + } + + /** + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $image + */ + public function setImage($image) + { + $this->image = $image; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/MatchCode.php b/packages/ep_products/Classes/Domain/Model/MatchCode.php new file mode 100644 index 0000000..d0d06ea --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/MatchCode.php @@ -0,0 +1,54 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class MatchCode extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $code = ''; + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + +} \ No newline at end of file diff --git a/packages/ep_products/Classes/Domain/Model/Officetip.php b/packages/ep_products/Classes/Domain/Model/Officetip.php new file mode 100644 index 0000000..fa00363 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Officetip.php @@ -0,0 +1,96 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Officetip extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $label = ''; + + /** + * @var string + */ + protected $text = ''; + + /** + * @var \EP\EpProducts\Domain\Model\Teammember + */ + protected $author; + + /** + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * @param string $label + */ + public function setLabel($label) + { + $this->label = $label; + } + + /** + * @return string $text + */ + public function getText() + { + return $this->text; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + /** + * @return Teammember $author + */ + public function getAuthor() + { + return $this->author; + } + + /** + * @param Teammember $author + */ + public function setAuthor(Teammember $author) + { + $this->author = $author; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Product.php b/packages/ep_products/Classes/Domain/Model/Product.php new file mode 100644 index 0000000..d268f7f --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Product.php @@ -0,0 +1,1209 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements TeaserInterface +{ + + /** + * @var bool + */ + protected $searchable = true; + + /** + * @var bool + */ + protected $bookable = true; + + /** + * @var bool + */ + protected $hideBookingButton = false; + + /** + * @var bool + */ + protected $daytrip = false; + + /** + * @var bool + */ + protected $important; + + /** + * @var bool + */ + protected $skipassIncluded = true; + + /** + * @var string + */ + protected $detailPage; + + /** + * @var string + */ + protected $externalLink = ''; + + /** + * @var string + */ + protected $tourLink; + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $nameInternal = ''; + + /** + * @var string + */ + protected $keywords = ''; + + /** + * @var string + */ + protected $headline = ''; + + /** + * @var int + */ + protected $busProId = 0; + + /** + * @var string + */ + protected $code = ''; + + /** + * @var string + */ + protected $season = 'w'; + + /** + * @var string + */ + protected $feature = ''; + + /** + * @var string + */ + protected $teaser = ''; + + /** + * @var string + */ + protected $teaserLong = ''; + + /** + * @var string + */ + protected $headerTitle = ''; + + /** + * @var string + */ + protected $headerSubtitle = ''; + + /** + * @var string + */ + protected $conceptDescription = ''; + + /** + * @var string + */ + protected $boardDescription = ''; + + /** + * @var string + */ + protected $additionalServices = ''; + + /** + * @var string + */ + protected $includedServices; + + /** + * @var string + */ + protected $programmeDescription = ''; + + /** + * @var string + */ + protected $skiPassAltTitle = ''; + + /** + * @var string + */ + protected $skiPassDescription = ''; + + /** + * @var float + */ + protected $ratingAverage = 0; + + /** + * @var int + */ + protected $ratingVotesCount = 0; + + /** + * @var string + */ + protected $testimonials = ''; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $images; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $resellerImages; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $teaserImages; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $headerImages; + + /** + * @var FileReference + */ + protected $tourThumbnail; + + /** + * @var \EP\EpProducts\Domain\Model\Concept + */ + protected $concept; + + /** + * @var \EP\EpProducts\Domain\Model\Country + */ + protected $country; + + /** + * @var \EP\EpProducts\Domain\Model\Region + */ + protected $region; + + /** + * @var \EP\EpProducts\Domain\Model\City + */ + protected $city; + + /** + * @var \EP\EpProducts\Domain\Model\Journey + */ + protected $journey; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> + */ + protected $facts; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Hotel> + */ + protected $hotels; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Faq> + */ + protected $faqs; + + /** + * @var \EP\EpProducts\Domain\Model\Officetip + */ + protected $officetip; + + /** + * @var \EP\EpProducts\Domain\Model\Hotel + */ + protected $calendarHotel; + + /** + * @var \EP\EpProducts\Domain\Model\Product + */ + protected $altProduct; + + /** + * @var string + */ + protected $altLabel; + + /** + * @var string + */ + protected $video = ''; + + /** + * @var \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + protected $banner; + + /** + * @var \EP\EpProducts\Domain\Model\Badge + */ + protected $badge; + + /** + * @var string + */ + protected $nonBookableDates; + + /** + * @var int + */ + protected $minPrice; + + /** + * @var \EP\EpProducts\Domain\Model\Infobox + */ + protected $nonBookableInfo; + + public function __construct() + { + $this->initStorageObjects(); + } + + protected function initStorageObjects() + { + $this->images = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->teaserImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->headerImages = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->facts = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->hotels = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->faqs = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return bool + */ + public function getSearchable() + { + return $this->searchable; + } + + /** + * @param bool $searchable + */ + public function setSearchable($searchable) + { + $this->searchable = (bool) $searchable; + } + + /** + * @return boolean + */ + public function getBookable() + { + return (bool) $this->bookable; + } + + /** + * @param boolean $bookable + */ + public function setBookable($bookable) + { + $this->bookable = (bool) $bookable; + } + + /** + * @return bool + */ + public function getHideBookingButton() + { + return $this->hideBookingButton; + } + + /** + * @param bool $hideBookingButton + */ + public function setHideBookingButton($hideBookingButton) + { + $this->hideBookingButton = (bool) $hideBookingButton; + } + + /** + * @return bool + */ + public function isDaytrip() + { + return (bool) $this->daytrip; + } + + /** + * @param bool $daytrip + */ + public function setDaytrip($daytrip) + { + $this->daytrip = (bool) $daytrip; + } + + /** + * @return bool + */ + public function isImportant() + { + return $this->important; + } + + /** + * @param bool $important + */ + public function setImportant($important) + { + $this->important = $important; + } + + /** + * @return bool + */ + public function isSkipassIncluded() + { + return $this->skipassIncluded; + } + + /** + * @param bool $skipassIncluded + */ + public function setSkipassIncluded($skipassIncluded) + { + $this->skipassIncluded = $skipassIncluded; + } + + /** + * @return string + */ + public function getDetailPage() { + return $this->detailPage; + } + + /** + * @param string $detailPage + */ + public function setDetailPage($detailPage) { + $this->detailPage = $detailPage; + } + + /** + * @return string + */ + public function getExternalLink() + { + return $this->externalLink; + } + + /** + * @param string $externalLink + */ + public function setExternalLink($externalLink) + { + $this->externalLink = $externalLink; + } + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getNameInternal() + { + return $this->nameInternal; + } + + /** + * @param string $nameInternal + */ + public function setNameInternal($nameInternal) + { + $this->nameInternal = $nameInternal; + } + + /** + * @return string + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * @param string $keywords + */ + public function setKeywords($keywords) + { + $this->keywords = $keywords; + } + + /** + * @return string + */ + public function getHeadline() { + return $this->headline; + } + + /** + * @param string $headline + */ + public function setHeadline($headline) { + $this->headline = $headline; + } + + /** + * @return int + */ + public function getBusProId() + { + return $this->busProId; + } + + /** + * @param int $busProId + */ + public function setBusProId($busProId) + { + $this->busProId = $busProId; + } + + /** + * @return string $code + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @return string + */ + public function getSeason() + { + return $this->season; + } + + /** + * @param string $season + */ + public function setSeason($season) + { + $this->season = $season; + } + + /** + * @return string + */ + public function getFeature() + { + return $this->feature; + } + + /** + * @param string $feature + */ + public function setFeature($feature) + { + $this->feature = $feature; + } + + /** + * @return string + */ + public function getTeaser() { + return $this->teaser; + } + + /** + * @param string $teaser + */ + public function setTeaser($teaser) { + $this->teaser = $teaser; + } + + /** + * @return string + */ + public function getTeaserLong() + { + return $this->teaserLong; + } + + /** + * @param string $teaserLong + */ + public function setTeaserLong($teaserLong) + { + $this->teaserLong = $teaserLong; + } + + /** + * @return string + */ + public function getHeaderTitle() + { + return $this->headerTitle; + } + + /** + * @param string $headerTitle + */ + public function setHeaderTitle($headerTitle) + { + $this->headerTitle = $headerTitle; + } + + /** + * @return string + */ + public function getHeaderSubtitle() + { + return $this->headerSubtitle; + } + + /** + * @param string $headerSubtitle + */ + public function setHeaderSubtitle($headerSubtitle) + { + $this->headerSubtitle = $headerSubtitle; + } + + /** + * @return string + */ + public function getConceptDescription() { + return $this->conceptDescription; + } + + /** + * @param string $conceptDescription + */ + public function setConceptDescription($conceptDescription) { + $this->conceptDescription = $conceptDescription; + } + + /** + * @return string + */ + public function getBoardDescription() { + return $this->boardDescription; + } + + /** + * @param string $boardDescription + */ + public function setBoardDescription($boardDescription) { + $this->boardDescription = $boardDescription; + } + + /** + * @return string + */ + public function getAdditionalServices() { + return $this->additionalServices; + } + + /** + * @param string $additionalServices + */ + public function setAdditionalServices($additionalServices) { + $this->additionalServices = $additionalServices; + } + + /** + * @return string + */ + public function getIncludedServices() + { + return $this->includedServices; + } + + /** + * @param string $includedServices + */ + public function setIncludedServices($includedServices) + { + $this->includedServices = $includedServices; + } + + /** + * @return string + */ + public function getProgrammeDescription() { + return $this->programmeDescription; + } + + /** + * @param string $programmeDescription + */ + public function setProgrammeDescription($programmeDescription) { + $this->programmeDescription = $programmeDescription; + } + + /** + * @return string + */ + public function getSkiPassAltTitle() + { + return $this->skiPassAltTitle; + } + + /** + * @param string $skiPassAltTitle + */ + public function setSkiPassAltTitle($skiPassAltTitle) + { + $this->skiPassAltTitle = $skiPassAltTitle; + } + + /** + * @return string + */ + public function getSkiPassDescription() { + return $this->skiPassDescription; + } + + /** + * @param string $skiPassDescription + */ + public function setSkiPassDescription($skiPassDescription) { + $this->skiPassDescription = $skiPassDescription; + } + + /** + * @return float + */ + public function getRatingAverage() + { + return $this->ratingAverage; + } + + /** + * @param float $ratingAverage + */ + public function setRatingAverage($ratingAverage) + { + $this->ratingAverage = $ratingAverage; + } + + /** + * @return int + */ + public function getRatingVotesCount() + { + return $this->ratingVotesCount; + } + + /** + * @param int $ratingVotesCount + */ + public function setRatingVotesCount($ratingVotesCount) + { + $this->ratingVotesCount = $ratingVotesCount; + } + + /** + * @return string + */ + public function getTestimonials() + { + return $this->testimonials; + } + + /** + * @param string $testimonials + */ + public function setTestimonials($testimonials) + { + $this->testimonials = $testimonials; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference $images> + */ + public function getImages() + { + return $this->images; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $images + */ + public function setImages(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $images) + { + $this->images = $images; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference $resellerImages> + */ + public function getResellerImages() + { + if (!$this->resellerImages) { + return $this->images; + } + + return $this->resellerImages; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $images + */ + public function setResellerImages(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $images) + { + $this->resellerImages = $images; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImages() + { + return $this->teaserImages; + } + + /** + * @return null|\TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImage() + { + $teaserImages = $this->teaserImages; + $teaserImages->rewind(); + + if ($teaserImages->count() === 0) { + return null; + } + + return $teaserImages->current(); + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $teaserImages + */ + public function setTeaserImages($teaserImages) + { + $this->teaserImages = $teaserImages; + } + + /** + * Returns all header images with fallback to parent + * region in case none are assigned. + * + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImages() + { + $headerImages = $this->headerImages; + + if ($headerImages->count() === 0 && null !== $this->getRegion()) { + $headerImages = $this->getRegion()->getHeaderImages(); + } + + return $headerImages; + } + + /** + * Returns a single header image with fallback to parent + * region in case none is assigned. + * + * @return null|\TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getHeaderImage() + { + $headerImages = $this->getHeaderImages(); + $headerImages->rewind(); + + if ($headerImages->count() === 0) { + return null; + } + + return $headerImages->current(); + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $headerImages + */ + public function setHeaderImages($headerImages) + { + $this->headerImages = $headerImages; + } + + /** + * @return \EP\EpProducts\Domain\Model\Concept $concept + */ + public function getConcept() + { + return $this->concept; + } + + /** + * @param \EP\EpProducts\Domain\Model\Concept $concept + */ + public function setConcepts(\EP\EpProducts\Domain\Model\Concept $concept) + { + $this->concept = $concept; + } + + /** + * @return \EP\EpProducts\Domain\Model\Country $country + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param \EP\EpProducts\Domain\Model\Country $country + */ + public function setCountry(\EP\EpProducts\Domain\Model\Country $country) + { + $this->country = $country; + } + + /** + * @return \EP\EpProducts\Domain\Model\Region + */ + public function getRegion() + { + return $this->region; + } + + /** + * @param \EP\EpProducts\Domain\Model\Region $region + */ + public function setRegion(\EP\EpProducts\Domain\Model\Region $region) + { + $this->region = $region; + } + + /** + * @return \EP\EpProducts\Domain\Model\City + */ + public function getCity() + { + return $this->city; + } + + /** + * @param \EP\EpProducts\Domain\Model\City $city + */ + public function setCity(\EP\EpProducts\Domain\Model\City $city) + { + $this->city = $city; + } + + /** + * @return \EP\EpProducts\Domain\Model\Journey + */ + public function getJourney() + { + return $this->journey; + } + + /** + * @param \EP\EpProducts\Domain\Model\Journey $journey + */ + public function setJourney($journey) + { + $this->journey = $journey; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function getFacts() + { + return $this->facts; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function setFacts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $facts) + { + $this->facts = $facts; + } + + /** + * @param \EP\EpProducts\Domain\Model\Hotel $hotel + */ + public function addHotel(\EP\EpProducts\Domain\Model\Hotel $hotel) + { + $this->hotels->attach($hotel); + } + + /** + * @param \EP\EpProducts\Domain\Model\Hotel $hotel + */ + public function removeHotel(\EP\EpProducts\Domain\Model\Hotel $hotel) + { + $this->hotels->detach($hotel); + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Hotel> $hotels + */ + public function getHotels() + { + return $this->hotels; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Hotel> $hotels + */ + public function setHotels(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $hotels) + { + $this->hotels = $hotels; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Faq> + */ + public function getFaqs() { + return $this->faqs; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Faq> $faq + */ + public function setFaqs(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $faqs) + { + $this->faqs = $faqs; + } + + /** + * @return Officetip + */ + public function getOfficetip() + { + return $this->officetip; + } + + /** + * @param Officetip $officetip + */ + public function setOfficetip($officetip) + { + $this->officetip = $officetip; + } + + /** + * @return Hotel + */ + public function getCalendarHotel() + { + return $this->calendarHotel; + } + + /** + * @param Hotel $calendarHotel + */ + public function setCalendarHotel($calendarHotel) + { + $this->calendarHotel = $calendarHotel; + } + + /** + * @return Product + */ + public function getAltProduct() + { + return $this->altProduct; + } + + /** + * @param Product $altProduct + */ + public function setAltProduct($altProduct) + { + $this->altProduct = $altProduct; + } + + /** + * @return string + */ + public function getAltLabel() + { + return $this->altLabel; + } + + /** + * @param string $altLabel + */ + public function setAltLabel($altLabel) + { + $this->altLabel = $altLabel; + } + + /** + * @return string + */ + public function getVideo() + { + return $this->video; + } + + /** + * @param string $video + */ + public function setVideo($video) + { + $this->video = $video; + } + + /** + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getBanner() + { + return $this->banner; + } + + /** + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $banner + */ + public function setBanner($banner) + { + $this->banner = $banner; + } + + /** + * @return Badge + */ + public function getBadge() + { + return $this->badge; + } + + /** + * @param Badge $badge + */ + public function setBadge($badge) + { + $this->badge = $badge; + } + + /** + * @return string + */ + public function getNonBookableDates() + { + return $this->nonBookableDates; + } + + /** + * @return int + */ + public function getMinPrice(): int + { + return $this->minPrice; + } + + /** + * @param int $minPrice + */ + public function setMinPrice($minPrice) + { + $this->minPrice = $minPrice; + } + + /** + * @return Infobox + */ + public function getNonBookableInfo() + { + return $this->nonBookableInfo; + } + + /** + * @param Infobox $nonBookableInfo + */ + public function setNonBookableInfo($nonBookableInfo) + { + $this->nonBookableInfo = $nonBookableInfo; + } + + public function getTourThumbnail() + { + return $this->tourThumbnail; + } + + public function setTourThumbnail(FileReference $tourThumbnail) + { + $this->tourThumbnail = $tourThumbnail; + } + + public function getTourLink() + { + return $this->tourLink; + } + + public function setTourLink(string $tourLink) + { + $this->tourLink = $tourLink; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Region.php b/packages/ep_products/Classes/Domain/Model/Region.php new file mode 100644 index 0000000..4f50d29 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Region.php @@ -0,0 +1,476 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Region extends AbstractDestination implements TeaserInterface +{ + + /** + * @var bool + */ + protected $excludeFromTeasergroups = false; + + /** + * @var string + */ + protected $skiArea = ''; + + /** + * @var string + */ + protected $skiAreaExtended = ''; + + /** + * @var string + */ + protected $skiPass = ''; + + /** + * @var string + */ + protected $news = ''; + + /** + * @var string + */ + protected $length = ''; + + /** + * @var string + */ + protected $height = ''; + + /** + * @var string + */ + protected $lifts = ''; + + /** + * @var string + */ + protected $feature = ''; + + /** + * @var \EP\EpProducts\Domain\Model\Country + */ + protected $country; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\City> + * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") + */ + protected $cities; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Product> + * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") + */ + protected $products; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> + */ + protected $facts; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Hotel> + * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") + */ + protected $hotels; + + /** + * @var \EP\EpProducts\Domain\Model\Snowreport + */ + protected $snowreport; + + /** + * @var \EP\EpProducts\Domain\Model\Webcam + */ + protected $webcam; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + protected $regionMaps; + + /** + * @var \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + protected $awardImage; + + /** + * @var string + */ + protected $awardLink; + + /** + * @var string + */ + protected $awardLabel; + + protected function initStorageObjects() + { + parent::initStorageObjects(); + $this->cities = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->products = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->facts = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->hotels = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + $this->regionMaps = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); + } + + /** + * @return bool + */ + public function isExcludeFromTeasergroups() + { + return $this->excludeFromTeasergroups; + } + + /** + * @param bool $excludeFromTeasergroups + */ + public function setExcludeFromTeasergroups($excludeFromTeasergroups) + { + $this->excludeFromTeasergroups = (bool) $excludeFromTeasergroups; + } + + /** + * @return string + */ + public function getSkiArea() { + return $this->skiArea; + } + + /** + * @param string $skiArea + */ + public function setSkiArea($skiArea) { + $this->skiArea = $skiArea; + } + + /** + * @return string + */ + public function getSkiAreaExtended() + { + return $this->skiAreaExtended; + } + + /** + * @param string $skiAreaExtended + */ + public function setSkiAreaExtended($skiAreaExtended) + { + $this->skiAreaExtended = $skiAreaExtended; + } + + /** + * @return string + */ + public function getSkiPass() { + return $this->skiPass; + } + + /** + * @param string $skiPass + */ + public function setSkiPass($skiPass) { + $this->skiPass = $skiPass; + } + + /** + * @return string + */ + public function getNews() { + return $this->news; + } + + /** + * @param string $news + */ + public function setNews($news) { + $this->news = $news; + } + + /** + * @return string + */ + public function getLength() { + return $this->length; + } + + /** + * @param string $length + */ + public function setLength($length) { + $this->length = $length; + } + + /** + * @return string + */ + public function getHeight() { + return $this->height; + } + + /** + * @param string $height + */ + public function setHeight($height) { + $this->height = $height; + } + + /** + * @return string + */ + public function getLifts() { + return $this->lifts; + } + + /** + * @param string $lifts + */ + public function setLifts($lifts) { + $this->lifts = $lifts; + } + + /** + * @return string + */ + public function getFeature() + { + return $this->feature; + } + + /** + * @param string $feature + */ + public function setFeature($feature) + { + $this->feature = $feature; + } + + /** + * Returns all header images with fallback to parent + * country in case none are assigned. + * + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHeaderImages() + { + $headerImages = $this->headerImages; + + if ($headerImages->count() === 0 && null !== $this->getCountry()) { + $headerImages = $this->getCountry()->getHeaderImages(); + } + + return $headerImages; + } + + /** + * @return \EP\EpProducts\Domain\Model\Country $country + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param \EP\EpProducts\Domain\Model\Country $country + */ + public function setCountry(\EP\EpProducts\Domain\Model\Country $country) + { + $this->country = $country; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getCities() { + return $this->cities; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $cities + */ + public function setCities($cities) { + $this->cities = $cities; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getProducts() { + return $this->products; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $products + */ + public function setProducts($products) { + $this->products = $products; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function getFacts() + { + return $this->facts; + } + + /** + * Sets the facts + * + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Fact> $facts + */ + public function setFacts(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $facts) + { + $this->facts = $facts; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getHotels() { + return $this->hotels; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $hotels + */ + public function setHotels($hotels) { + $this->hotels = $hotels; + } + + /** + * @return Snowreport + */ + public function getSnowreport() + { + return $this->snowreport; + } + + /** + * @param Snowreport $snowreport + */ + public function setSnowreport($snowreport) + { + $this->snowreport = $snowreport; + } + + /** + * @return Webcam + */ + public function getWebcam() + { + return $this->webcam; + } + + /** + * @return bool + */ + public function getWebcamAvailable() + { + $webcam = $this->getWebcam(); + return $webcam !== null && $webcam->getAvailable(); + } + + /** + * @param Webcam $webcam + */ + public function setWebcam($webcam) + { + $this->webcam = $webcam; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + public function getRegionMaps() + { + return $this->regionMaps; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> + */ + public function setRegionMaps($regionMaps) + { + $this->regionMaps = $regionMaps; + } + + /** + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getAwardImage() + { + return $this->awardImage; + } + + /** + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $awardImage + */ + public function setAwardImage($awardImage) + { + $this->awardImage = $awardImage; + } + + /** + * @return string + */ + public function getAwardLink() + { + return $this->awardLink; + } + + /** + * @param string $awardLink + */ + public function setAwardLink($awardLink) + { + $this->awardLink = $awardLink; + } + + /** + * @return string + */ + public function getAwardLabel() + { + return $this->awardLabel; + } + + /** + * @param string $awardLabel + */ + public function setAwardLabel($awardLabel) + { + $this->awardLabel = $awardLabel; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Reseller.php b/packages/ep_products/Classes/Domain/Model/Reseller.php new file mode 100644 index 0000000..7fbc41d --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Reseller.php @@ -0,0 +1,127 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Reseller extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $name; + + /** + * @var string + */ + protected $code; + + /** + * @var string + */ + protected $paCode; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Product> + * @Lazy() + */ + protected $products; + + public function __construct() + { + $this->products = new ObjectStorage(); + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * @return string + */ + public function getPaCode() + { + return $this->paCode; + } + + /** + * @param string $paCode + */ + public function setPaCode($paCode) + { + $this->paCode = $paCode; + } + + /** + * @return ObjectStorage + */ + public function getProducts() + { + return $this->products; + } + + /** + * @param ObjectStorage $products + */ + public function setProducts($products) + { + $this->products = $products; + } + +} + diff --git a/packages/ep_products/Classes/Domain/Model/Room.php b/packages/ep_products/Classes/Domain/Model/Room.php new file mode 100644 index 0000000..16c5f41 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Room.php @@ -0,0 +1,48 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Room +{ + + const ROOM_TYPE_UNCLASSIFIED = 0; + const ROOM_TYPE_FOR_ONE = 1; + const ROOM_TYPE_FOR_TWO = 2; + const ROOM_TYPE_FOR_THREE = 3; + const ROOM_TYPE_FOR_FOUR = 4; + const ROOM_TYPE_FOR_FIVE = 5; + const ROOM_TYPE_FOR_SIX = 6; + const ROOM_TYPE_FOR_SEVEN = 7; + const ROOM_TYPE_FOR_EIGHT = 8; + const ROOM_TYPE_FOR_NINE_PLUS = 9; + const ROOM_TYPE_WITH_OTHERS = 10; + + const ROOM_STATUS_AVAILABLE = 1; + const ROOM_STATUS_ON_REQUEST = 2; + const ROOM_STATUS_FULLY_BOOKED = 3; +} diff --git a/packages/ep_products/Classes/Domain/Model/SearchResult.php b/packages/ep_products/Classes/Domain/Model/SearchResult.php new file mode 100644 index 0000000..e4b8395 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/SearchResult.php @@ -0,0 +1,85 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class SearchResult +{ + + /** + * @var int + */ + protected $total = 0; + + /** + * @var array + */ + protected $data = []; + + /** + * @param array $data + * @param int $total + */ + public function __construct(array $data = [], $total = 0) + { + $this->data = $data; + $this->total = $total; + } + + /** + * @return int + */ + public function getTotal() + { + return $this->total; + } + + /** + * @param int $total + */ + public function setTotal($total) + { + $this->total = $total; + } + + /** + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * @param array $data + */ + public function setData($data) + { + $this->data = $data; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/Snowreport.php b/packages/ep_products/Classes/Domain/Model/Snowreport.php new file mode 100644 index 0000000..7497f42 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Snowreport.php @@ -0,0 +1,392 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Snowreport extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var int + */ + protected $vendorUid; + + /** + * @var string + */ + protected $regionName = ''; + + /** + * @var bool + */ + protected $glacier = false; + + /** + * @var int + */ + protected $heightMax = 0; + + /** + * @var int + */ + protected $heightMin = 0; + + /** + * @var bool + */ + protected $opened = false; + + /** + * @var int + */ + protected $snowLevelMountain = 0; + + /** + * @var int + */ + protected $snowLevelValley = 0; + + /** + * @var string + */ + protected $snowQuality = ''; + + /** + * @var \DateTime + */ + protected $lastSnowfall; + + /** + * @var int + */ + protected $liftsTotal = 0; + + /** + * @var int + */ + protected $liftsOpened = 0; + + /** + * @var \DateTime + */ + protected $lastUpdate; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Webcam> + */ + protected $webcams; + + /** + * @var \DateTime + */ + protected $seasonStart; + + /** + * @var \DateTime + */ + protected $seasonEnd; + + public function __construct() + { + $this->webcams = new ObjectStorage(); + } + + /** + * @return int + */ + public function getVendorUid() + { + return $this->vendorUid; + } + + /** + * @param int $vendorUid + */ + public function setVendorUid($vendorUid) + { + $this->vendorUid = $vendorUid; + } + + /** + * @return string + */ + public function getRegionName() + { + return $this->regionName; + } + + /** + * @param string $regionName + */ + public function setRegionName($regionName) + { + $this->regionName = $regionName; + } + + /** + * @return bool + */ + public function getGlacier() + { + return $this->glacier; + } + + /** + * @param bool $glacier + */ + public function setGlacier($glacier) + { + $this->glacier = $glacier; + } + + /** + * @return int + */ + public function getHeightMax() + { + return $this->heightMax; + } + + /** + * @param int $heightMax + */ + public function setHeightMax($heightMax) + { + $this->heightMax = $heightMax; + } + + /** + * @return int + */ + public function getHeightMin() + { + return $this->heightMin; + } + + /** + * @param int $heightMin + */ + public function setHeightMin($heightMin) + { + $this->heightMin = $heightMin; + } + + /** + * @return bool + */ + public function getOpened() + { + return $this->opened; + } + + /** + * @param bool $opened + */ + public function setOpened($opened) + { + $this->opened = $opened; + } + + /** + * @return int + */ + public function getSnowLevelMountain() + { + return $this->snowLevelMountain; + } + + /** + * @param int $snowLevelMountain + */ + public function setSnowLevelMountain($snowLevelMountain) + { + $this->snowLevelMountain = $snowLevelMountain; + } + + /** + * @return int + */ + public function getSnowLevelValley() + { + return $this->snowLevelValley; + } + + /** + * @param int $snowLevelValley + */ + public function setSnowLevelValley($snowLevelValley) + { + $this->snowLevelValley = $snowLevelValley; + } + + /** + * @return string + */ + public function getSnowQuality() + { + return $this->snowQuality; + } + + /** + * @param string $snowQuality + */ + public function setSnowQuality($snowQuality) + { + $this->snowQuality = $snowQuality; + } + + /** + * @return \DateTime + */ + public function getLastSnowfall() + { + return $this->lastSnowfall; + } + + /** + * @param \DateTime $lastSnowfall + */ + public function setLastSnowfall($lastSnowfall) + { + $this->lastSnowfall = $lastSnowfall; + } + + /** + * @return int + */ + public function getLiftsTotal() + { + return (int) $this->liftsTotal; + } + + /** + * @param int $liftsTotal + */ + public function setLiftsTotal($liftsTotal) + { + $this->liftsTotal = $liftsTotal; + } + + /** + * @return int + */ + public function getLiftsOpened() + { + return (int) $this->liftsOpened; + } + + /** + * @param int $liftsOpened + */ + public function setLiftsOpened($liftsOpened) + { + $this->liftsOpened = $liftsOpened; + } + + /** + * @return \DateTime + */ + public function getLastUpdate() + { + return $this->lastUpdate; + } + + /** + * @param \DateTime $lastUpdate + */ + public function setLastUpdate($lastUpdate) + { + $this->lastUpdate = $lastUpdate; + } + + /** + * @param \EP\EpProducts\Domain\Model\Webcam $webcam + */ + public function addWebcam(\EP\EpProducts\Domain\Model\Webcam $webcam) + { + $this->webcams->attach($webcam); + } + + /** + * @param \EP\EpProducts\Domain\Model\Webcam $webcam + */ + public function removeWebcam(\EP\EpProducts\Domain\Model\Webcam $webcam) + { + $this->webcams->detach($webcam); + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Webcam> + */ + public function getWebcams() + { + return $this->webcams; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\EP\EpProducts\Domain\Model\Webcam> $webcams + */ + public function setWebcams(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $webcams) + { + $this->webcams = $webcams; + } + + /** + * @return \DateTime + */ + public function getSeasonStart() + { + return $this->seasonStart; + } + + /** + * @param \DateTime $seasonStart + */ + public function setSeasonStart($seasonStart) + { + $this->seasonStart = $seasonStart; + } + + /** + * @return \DateTime + */ + public function getSeasonEnd() + { + return $this->seasonEnd; + } + + /** + * @param \DateTime $seasonEnd + */ + public function setSeasonEnd($seasonEnd) + { + $this->seasonEnd = $seasonEnd; + } +} + diff --git a/packages/ep_products/Classes/Domain/Model/Teammember.php b/packages/ep_products/Classes/Domain/Model/Teammember.php new file mode 100644 index 0000000..d7d1fff --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Teammember.php @@ -0,0 +1,187 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Teammember extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var string + */ + protected $name = ''; + + /** + * @var string + */ + protected $email = ''; + + /** + * @var string + */ + protected $phone = ''; + + /** + * @var string + */ + protected $tasks = ''; + + /** + * @var \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + protected $image; + + /** + * @var string + */ + protected $description = ''; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\Category> + */ + protected $categories; + + public function __construct() + { + $this->categories = new ObjectStorage(); + } + + /** + * @return string $name + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string $email + */ + public function getEmail() + { + return $this->email; + } + + /** + * @param string $email + */ + public function setEmail($email) + { + $this->email = $email; + } + + /** + * @return string + */ + public function getPhone() + { + return $this->phone; + } + + /** + * @param string $phone + */ + public function setPhone($phone) + { + $this->phone = $phone; + } + + /** + * @return string + */ + public function getTasks() + { + return $this->tasks; + } + + /** + * @param string $tasks + */ + public function setTasks($tasks) + { + $this->tasks = $tasks; + } + + /** + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference + */ + public function getImage() + { + return $this->image; + } + + /** + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $image + */ + public function setImage($image) + { + $this->image = $image; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\Category> + */ + public function getCategories() + { + return $this->categories; + } + + /** + * @param ObjectStorage $categories + */ + public function setCategories($categories) + { + $this->categories = $categories; + } + +} diff --git a/packages/ep_products/Classes/Domain/Model/TeaserInterface.php b/packages/ep_products/Classes/Domain/Model/TeaserInterface.php new file mode 100644 index 0000000..33ad37a --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/TeaserInterface.php @@ -0,0 +1,50 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +interface TeaserInterface +{ + + /** + * @return string + */ + public function getTeaser(); + + /** + * @return ObjectStorage + */ + public function getTeaserImages(); + + /** + * @return null|\TYPO3\CMS\Extbase\Persistence\ObjectStorage + */ + public function getTeaserImage(); + +} diff --git a/packages/ep_products/Classes/Domain/Model/Travelinfo.php b/packages/ep_products/Classes/Domain/Model/Travelinfo.php new file mode 100644 index 0000000..ad55bf2 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Travelinfo.php @@ -0,0 +1,329 @@ + + */ + protected $teasers; + + /** + * @var ObjectStorage + */ + protected $links; + + /** + * @var string + */ + protected $travelCode; + + /** + * @var Product + */ + protected $product; + + /** + * @var bool + */ + protected $hideAddress = false; + + public function __construct() + { + $this->initStorageObjects(); + } + + protected function initStorageObjects() + { + $this->teasers = new ObjectStorage(); + $this->links = new ObjectStorage(); + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + public function getHeaderImage() + { + return $this->headerImage; + } + + public function setHeaderImage($headerImage) + { + $this->headerImage = $headerImage; + + return $this; + } + + public function getFooterImage() + { + return $this->footerImage; + } + + public function setFooterImage($footerImage) + { + $this->footerImage = $footerImage; + + return $this; + } + + public function getSubline() + { + return $this->subline; + } + + public function setSubline($subline) + { + $this->subline = $subline; + + return $this; + } + + public function getHeadline() + { + return $this->headline; + } + + public function setHeadline($headline) + { + $this->headline = $headline; + + return $this; + } + + public function getIntroText() + { + return $this->introText; + } + + public function setIntroText($introText) + { + $this->introText = $introText; + + return $this; + } + + public function getBusInfoText() + { + return $this->busInfoText; + } + + public function setBusInfoText($busInfoText) + { + $this->busInfoText = $busInfoText; + + return $this; + } + + public function getDepartureText() + { + return $this->departureText; + } + + public function setDepartureText($departureText) + { + $this->departureText = $departureText; + + return $this; + } + + public function getFooterText() + { + return $this->footerText; + } + + public function setFooterText($footerText) + { + $this->footerText = $footerText; + + return $this; + } + + public function getAdditionalInfo() + { + return $this->additionalInfo; + } + + public function setAdditionalInfo($additionalInfo) + { + $this->additionalInfo = $additionalInfo; + + return $this; + } + + public function getImportantPhoneNumbers() + { + return $this->importantPhoneNumbers; + } + + public function setImportantPhoneNumbers($importantPhoneNumbers) + { + $this->importantPhoneNumbers = $importantPhoneNumbers; + + return $this; + } + + public function getImportantLinks() + { + return $this->importantLinks; + } + + public function setImportantLinks($importantLinks) + { + $this->importantLinks = $importantLinks; + + return $this; + } + + public function getSelfArrangedText() + { + return $this->selfArrangedText; + } + + public function setSelfArrangedText($selfArrangedText) + { + $this->selfArrangedText = $selfArrangedText; + + return $this; + } + + public function getTeasers() + { + return $this->teasers; + } + + public function setTeasers($teasers) + { + $this->teasers = $teasers; + } + + public function getLinks() + { + return $this->links; + } + + public function setLinks($links) + { + $this->links = $links; + + return $this; + } + + public function getTravelCode() + { + return $this->travelCode; + } + + public function setTravelCode($travelCode) + { + $this->travelCode = $travelCode; + + return $this; + } + + public function getProduct() + { + return $this->product; + } + + public function setProduct($product) + { + $this->product = $product; + return $this; + } + + public function isHideAddress() + { + return $this->hideAddress; + } + + public function setHideAddress($hideAddress) + { + $this->hideAddress = $hideAddress; + return $this; + } +} + diff --git a/packages/ep_products/Classes/Domain/Model/TravelinfoLink.php b/packages/ep_products/Classes/Domain/Model/TravelinfoLink.php new file mode 100644 index 0000000..b85bdcf --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/TravelinfoLink.php @@ -0,0 +1,61 @@ +title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + public function getLink() + { + return $this->link; + } + + public function setLink($link) + { + $this->link = $link; + } + + public function getIcon() + { + return $this->icon; + } + + public function setIcon($icon) + { + $this->icon = $icon; + } +} + diff --git a/packages/ep_products/Classes/Domain/Model/TravelinfoTeaser.php b/packages/ep_products/Classes/Domain/Model/TravelinfoTeaser.php new file mode 100644 index 0000000..ae15345 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/TravelinfoTeaser.php @@ -0,0 +1,76 @@ +title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + public function getBodytext() + { + return $this->bodytext; + } + + public function setBodytext($bodytext) + { + $this->bodytext = $bodytext; + } + + public function getCategory() + { + return $this->category; + } + + public function setCategory($category) + { + $this->category = $category; + } + + public function getImage() + { + return $this->image; + } + + public function setImage($image) + { + $this->image = $image; + } +} + diff --git a/packages/ep_products/Classes/Domain/Model/Webcam.php b/packages/ep_products/Classes/Domain/Model/Webcam.php new file mode 100644 index 0000000..52f873a --- /dev/null +++ b/packages/ep_products/Classes/Domain/Model/Webcam.php @@ -0,0 +1,138 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Webcam extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity +{ + + /** + * @var int + */ + protected $vendorUid; + + /** + * @var \EP\EpProducts\Domain\Model\Snowreport + */ + protected $snowreport; + + /** + * @var string + */ + protected $name; + + /** + * @var string + */ + protected $url; + + /** + * @var bool + */ + protected $available = true; + + /** + * @return Snowreport + */ + public function getSnowreport() + { + return $this->snowreport; + } + + /** + * @param Snowreport $snowreport + */ + public function setSnowreport($snowreport) + { + $this->snowreport = $snowreport; + } + + /** + * @return int + */ + public function getVendorUid() + { + return $this->vendorUid; + } + + /** + * @param int $vendorUid + */ + public function setVendorUid($vendorUid) + { + $this->vendorUid = $vendorUid; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string $url + */ + public function setUrl($url) + { + $this->url = $url; + } + + /** + * @return boolean + */ + public function getAvailable() + { + return $this->available; + } + + /** + * @param boolean $available + */ + public function setAvailable($available) + { + $this->available = $available; + } +} + diff --git a/packages/ep_products/Classes/Domain/Repository/AbstractRepository.php b/packages/ep_products/Classes/Domain/Repository/AbstractRepository.php new file mode 100644 index 0000000..ae9db36 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/AbstractRepository.php @@ -0,0 +1,230 @@ +objectManager->get(Typo3QuerySettings::class); + $querySettings->setRespectStoragePage(false); + $this->setDefaultQuerySettings($querySettings); + } + + /** + * @param array $uids + * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + public function getListByUids(array $uids = []) + { + $query = $this->createQuery(); + $query->matching( + $query->in('uid', $uids) + ); + return $query->execute(); + } + + protected function addFiltersettingsConditions + ( + QueryBuilder $qb, + array $filterSettings, + bool $constraintDates = true + ): void { + if ($constraintDates) { + $this->addDateFilterConditions($qb, $filterSettings); + } + $this->addPriceRangeFilterConditions($qb, $filterSettings); + $this->addCountryRegionAndCityFilterConditions($qb, $filterSettings); + $this->addConceptFilterConditions($qb, $filterSettings); + $this->addPaxFilterConditions($qb, $filterSettings); + $this->addDurationFilterConditions($qb, $filterSettings); + $this->addHotelTypesFilterConditions($qb, $filterSettings); + $this->addBoardTypesFilterConditions($qb, $filterSettings); + $this->addRoomTypesFilterConditions($qb, $filterSettings); + $this->addTransportationFilterConditions($qb, $filterSettings); + + // Product + if (!empty($filterSettings['productUid'])) { + $qb->andWhere($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter((int)$filterSettings['productUid'], Connection::PARAM_INT) + )); + } + + // Hotel + if (!empty($filterSettings['hotelUid'])) { + $qb->andWhere($qb->expr()->eq( + 'date.hotel', + $qb->createNamedParameter((int)$filterSettings['hotelUid'], Connection::PARAM_INT) + )); + } + } + + protected function addDateFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Date Start + if (!empty($filterSettings['dateFrom'])) { + $qb->andWhere($qb->expr()->gte( + 'date.date_start', + $qb->createNamedParameter($filterSettings['dateFrom']) + )); + } + + // Date End + if (!empty($filterSettings['dateTo'])) { + $qb->andWhere($qb->expr()->lte( + 'date.date_end', + $qb->createNamedParameter($filterSettings['dateTo']) + )); + } + } + + protected function addHotelFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Hotel + if ($filterSettings['hotelUid'] > 0) { + $qb->andWhere($qb->expr()->eq( + 'date.hotel', + $qb->createNamedParameter((int)$filterSettings['hotelUid'], Connection::PARAM_INT) + )); + } + + // Hotel types + if (isset($filterSettings['hotelTypes']) && is_array($filterSettings['hotelTypes']) && count($filterSettings['hotelTypes']) > 0) { + $qb->andWhere($qb->expr()->in( + 'date.hotel_type', + $qb->createNamedParameter($filterSettings['hotelTypes'], Connection::PARAM_INT_ARRAY) + )); + } + } + + protected function addCountryRegionAndCityFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + foreach (['city', 'region', 'country'] as $column) { + $key = $column . 'Uids'; + if (isset($filterSettings[$key]) && is_array($filterSettings[$key]) && count($filterSettings[$key]) > 0) { + $qb->andWhere($qb->expr()->in( + 'date.' . $column, + $qb->createNamedParameter($filterSettings[$key], Connection::PARAM_INT_ARRAY) + )); + break; + } + } + } + + protected function addConceptFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + if (isset($filterSettings['conceptUids']) && is_array($filterSettings['conceptUids']) && count($filterSettings['conceptUids']) > 0) { + $qb->andWhere($qb->expr()->in( + 'date.concept', + $qb->createNamedParameter($filterSettings['conceptUids'], Connection::PARAM_INT_ARRAY) + )); + } + } + + protected function addPaxFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Pax + if ($filterSettings['pax'] > 0) { + $qb->andWhere($qb->expr()->lte( + 'date.min_pax', + $qb->createNamedParameter($filterSettings['pax'], Connection::PARAM_INT) + )); + } + } + + protected function addPriceRangeFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Price ranges + if (isset($filterSettings['priceRanges']) && is_array($filterSettings['priceRanges']) && count($filterSettings['priceRanges']) > 0) { + $orX = $qb->expr()->orX(); + foreach ($filterSettings['priceRanges'] as $priceRange) { + [ $priceMin, $priceMax ] = FilterOptions::$priceRangeOptions[$priceRange]; + $orX->add($qb->expr()->andX( + $qb->expr()->gte( + 'date.min_price', + $qb->createNamedParameter($priceMin, Connection::PARAM_INT) + ), + $qb->expr()->lt( + 'date.min_price', + $qb->createNamedParameter($priceMax, Connection::PARAM_INT) + ) + )); + } + $qb->andWhere($orX); + } + } + + protected function addDurationFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Duration + if (0 === $filterSettings['nights']) { + return; + } + $nights = $filterSettings['nights']; + if ($nights === FilterService::DURATION_SHORT) { + $qb->andWhere($qb->expr()->lte( + 'date.nights', + $qb->createNamedParameter($nights, Connection::PARAM_INT)) + ); + } elseif ($nights === FilterService::DURATION_LONG) { + $qb->andWhere($qb->expr()->gte( + 'date.nights', + $qb->createNamedParameter($nights, Connection::PARAM_INT)) + ); + } + } + + protected function addHotelTypesFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Hotel types + if (isset($filterSettings['hotelTypes']) && is_array($filterSettings['hotelTypes']) && count($filterSettings['hotelTypes']) > 0) { + $qb->andWhere($qb->expr()->in( + 'date.hotel_type', + $qb->createNamedParameter($filterSettings['hotelTypes'], Connection::PARAM_INT_ARRAY) + )); + } + } + + protected function addBoardTypesFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Board types + if (isset($filterSettings['boardTypes']) && is_array($filterSettings['boardTypes']) && count($filterSettings['boardTypes']) > 0) { + $qb->andWhere($qb->expr()->in( + 'date.board_bus_pro_id', + $qb->createNamedParameter($filterSettings['boardTypes'], Connection::PARAM_INT_ARRAY) + )); + } + } + + protected function addRoomTypesFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Room types + if (isset($filterSettings['roomTypes']) && is_array($filterSettings['roomTypes']) && count($filterSettings['roomTypes']) > 0) { + $qb->andWhere($qb->expr()->in( + 'room.type', + $qb->createNamedParameter($filterSettings['roomTypes'], Connection::PARAM_INT_ARRAY) + )); + } + } + + protected function addTransportationFilterConditions(QueryBuilder $qb, array $filterSettings): void + { + // Transportation + if (false === empty($filterSettings['bus'])) { + $qb->andWhere($qb->expr()->eq('date.bus_available', 1)); + } + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/CityRepository.php b/packages/ep_products/Classes/Domain/Repository/CityRepository.php new file mode 100644 index 0000000..ba268ec --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/CityRepository.php @@ -0,0 +1,59 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class CityRepository extends AbstractRepository +{ + public function getTeasersByCountryOrRegionAndSeason(int $regionUid = 0, int $countryUid = 0, string $season = 'w', int $storagePid = 0) + { + if ($season === null) { + $season = AbstractDestination::SEASON_WINTER; + } + $query = $this->createQuery(); + $constraints = [ + $query->equals('season', $season), + $query->equals('excludeFromTeasergroups', 0) + ]; + if (0 < $regionUid) { + $constraints[] = $query->equals('region', $regionUid); + } elseif (0 < $countryUid) { + $constraints[] = $query->equals('country', $countryUid); + } + if (0 < $storagePid) { + $constraints[] = $query->equals('pid', $storagePid); + } + + return $query + ->matching($query->logicalAnd($constraints)) + ->execute() + ; + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/ConceptRepository.php b/packages/ep_products/Classes/Domain/Repository/ConceptRepository.php new file mode 100644 index 0000000..31c17f4 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/ConceptRepository.php @@ -0,0 +1,31 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class ConceptRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Repository/ContingentRepository.php b/packages/ep_products/Classes/Domain/Repository/ContingentRepository.php new file mode 100644 index 0000000..527df86 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/ContingentRepository.php @@ -0,0 +1,172 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use CalendR\Event\EventInterface; +use CalendR\Event\Provider\ProviderInterface; +use Doctrine\DBAL\DBALException; +use EP\EpProducts\Domain\Model\Contingent; +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class ContingentRepository extends AbstractRepository implements ProviderInterface +{ + /** + * @return EventInterface[] + * + * @throws DBALException + */ + public function getEvents(\DateTime $begin, ?\DateTime $end = null, array $options = []): array + { + $optionsResolver = new OptionsResolver(); + $optionsResolver->setRequired(['hotel']); + $optionsResolver->setAllowedTypes('hotel', Hotel::class); + $resolvedOptions = $optionsResolver->resolve($options); + + /** @var Hotel $hotel */ + $hotel = $resolvedOptions['hotel']; + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select('c.status as status', 'c.date as date', 'c.min_price as minPrice', 'c.pax', 'c.available', 'c.room_code') + ->from('tx_epproducts_domain_model_contingent', 'c') + ->where('c.hotel = :hotelUid') + ->andWhere('c.date >= :dateFrom') + ->setParameter('hotelUid', $hotel->getUid()) + ->setParameter('dateFrom', $begin->format('Y-m-d')) + ->orderBy('c.date') + ; + + if (null !== $end) { + $qb + ->andWhere('date <= :dateTo') + ->setParameter('dateTo', $end->format('Y-m-d')) + ; + } + + $result = $qb->execute()->fetchAllAssociative(); + + $dates = []; + + foreach ($result as $row) { + if (!\array_key_exists($row['date'], $dates)) { + $dates[$row['date']] = [ + 'status' => Contingent::STATUS_OK, + 'date' => $row['date'], + 'pax' => 0, + 'available' => 0, + ]; + } + if (Contingent::STATUS_OK !== $row['status']) { + $dates[$row['date']]['status'] = $row['status']; + } + if ('BelKal' !== $row['room_code']) { + $dates[$row['date']]['pax'] += $row['pax']; + $dates[$row['date']]['available'] += $row['available']; + } + } + + $events = []; + + foreach ($dates as $row) { + $row['hotel'] = $hotel; + $events[] = Contingent::fromArray($row); + } + + return $events; + } + + /** + * @return array + * + * @throws DBALException + */ + public function getAvailableContingents(Hotel $hotel, Product $product) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select('c.date as date', 'c.min_nights as minNights') + ->addSelectLiteral('SUM(c.available) as total') + ->addSelectLiteral('SUM(c.pax) as capacity') + ->from('tx_epproducts_domain_model_contingent', 'c') + ->innerJoin('c', 'tx_epproducts_domain_model_date', 'd', 'c.date = d.date_start AND c.hotel_code = d.hotel_code') + ->where('c.hotel = :hotelUid') + ->andWhere('d.product = :productUid') + ->andWhere('c.available > 0') + ->andWhere('c.min_price > 0') + ->setParameters([ + 'hotelUid' => $hotel->getUid(), + 'productUid' => $product->getUid(), + ]) + ->groupBy('c.date') + ->orderBy('c.date') + ; + + return $qb->execute()->fetchAllAssociative(); + } + + /** + * @return array + * + * @throws DBALException + */ + public function getAvailableRooms(string $dateFrom, string $dateTo, Hotel $hotel, Product $product) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select('c.date as date', 'c.room_code as code', 'c.room_label as label', 'c.available as available', + 'c.min_price as price', 'c.hotel_bus_pro_id as hotelBusProId', 'c.min_nights as minNights', + 'c.additional_night_min_price as additionalNightPrice', + 'c.additional_night_min_nights as additionalNightMinNights', 'd.bus_pro_id as busProId', + 'd.services_included as servicesIncluded', 'd.services_general as servicesOptional', + 'd.skipass_included as skiPassIncluded', 'd.season as season') + ->from('tx_epproducts_domain_model_contingent', 'c') + ->innerJoin('c', 'tx_epproducts_domain_model_date', 'd', 'c.date = d.date_start AND c.hotel_code = d.hotel_code') + ->where('c.hotel = :hotelUid') + ->andWhere('d.product = :productUid') + ->andWhere('c.date >= :dateFrom') + ->andWhere('c.date <= :dateTo') + ->andWhere('c.min_price > 0') + ->setParameters([ + 'hotelUid' => $hotel->getUid(), + 'productUid' => $product->getUid(), + 'dateFrom' => $dateFrom, + 'dateTo' => $dateTo, + ]) + ->groupBy('c.room_code') + ->orderBy('c.pax') + ->addOrderBy('c.min_price') + ; + + return $qb->execute()->fetchAllAssociative(); + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/CountryRepository.php b/packages/ep_products/Classes/Domain/Repository/CountryRepository.php new file mode 100644 index 0000000..ec29a51 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/CountryRepository.php @@ -0,0 +1,31 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class CountryRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Repository/DateRepository.php b/packages/ep_products/Classes/Domain/Repository/DateRepository.php new file mode 100644 index 0000000..c047213 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/DateRepository.php @@ -0,0 +1,45 @@ +createQuery(); + + return $query + ->matching($query->logicalAnd([ + $query->equals('product', $product), + $query->equals('dateStart', $date), + ])) + ->execute() + ; + } + + /** + * @return string[] serialized pickups of each upcoming date, latest first + */ + public function findPickupsForProductAndHotel(int $productUid, int $hotelUid): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + return $qb + ->select('date.pickups') + ->from('tx_epproducts_domain_model_date', 'date') + ->where( + $qb->expr()->eq('date.product', $qb->createNamedParameter($productUid, Connection::PARAM_INT)), + $qb->expr()->eq('date.hotel', $qb->createNamedParameter($hotelUid, Connection::PARAM_INT)), + $qb->expr()->eq('date.group_travel', $qb->createNamedParameter(0, Connection::PARAM_INT)), + 'date.date_start >= NOW()' + ) + ->orderBy('date.date_start', 'DESC') + ->addOrderBy('date.uid', 'DESC') + ->execute() + ->fetchFirstColumn() + ; + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/FaqRepository.php b/packages/ep_products/Classes/Domain/Repository/FaqRepository.php new file mode 100644 index 0000000..ffd7e7a --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/FaqRepository.php @@ -0,0 +1,62 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Faq; +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper; + +class FaqRepository extends AbstractRepository +{ + + /** + * @param string $uids + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function findByUids($uids) + { + /** @var DataMapper $dataMapper */ + $dataMapper = $this->objectManager->get(DataMapper::class); + $qb = $this->getDbConnection()->createQueryBuilder(); + + $rows = $qb + ->select('*') + ->from('tx_epproducts_domain_model_faq') + ->where('uid IN (:uids)') + ->add('orderBy', 'FIELD(uid, ' . $uids . ')') + ->setParameter('uids', GeneralUtility::trimExplode(',', $uids), Connection::PARAM_INT_ARRAY) + ->execute() + ->fetchAllAssociative() + ; + + return $dataMapper->map(Faq::class, $rows); + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/GroupsPriceOptionRepository.php b/packages/ep_products/Classes/Domain/Repository/GroupsPriceOptionRepository.php new file mode 100644 index 0000000..0148617 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/GroupsPriceOptionRepository.php @@ -0,0 +1,56 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; +use TYPO3\CMS\Extbase\Persistence\Repository; + +class GroupsPriceOptionRepository extends Repository +{ + /** + * @param array $uids + * @return array + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + public function findByUids(array $uids) + { + if (0 === count($uids)) { + return []; + } + + /** @var Typo3QuerySettings $querySettings */ + $querySettings = $this->objectManager->get(Typo3QuerySettings::class); + $querySettings->setRespectStoragePage(false); + $this->setDefaultQuerySettings($querySettings); + + $query = $this->createQuery(); + $query->matching($query->in('uid', $uids)); + + return $query->execute()->toArray(); + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/HotelRepository.php b/packages/ep_products/Classes/Domain/Repository/HotelRepository.php new file mode 100644 index 0000000..0f4972d --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/HotelRepository.php @@ -0,0 +1,272 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use Symfony\Component\OptionsResolver\OptionsResolver; +use TYPO3\CMS\Core\Database\Connection; + +class HotelRepository extends AbstractRepository +{ + + /** + * @param int $hotelUid + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getSingleByUid($hotelUid) + { + $qb = $this->getQueryBuilder(); + $qb + ->andWhere('date.hotel = :hotelUid') + ->orderBy('date.min_price') + ->setParameter('hotelUid', $hotelUid) + ->setMaxResults(1) + ; + + return $qb->execute()->fetchAssociative(); + } + + /** + * @param int $hotelUid + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getDateIndependentSingleByUid($hotelUid) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'hotel.uid as hotelUid', 'hotel.name as hotelName', 'hotel.teaser as hotelTeaser', + 'hotel.category as hotelCategory', 'hotel.detail_page as hotelDetailPage', + 'country.code as countryCode', 'country.name as country', + 'region.name as region' + ) + ->from('tx_epproducts_domain_model_hotel', 'hotel') + ->leftJoin('hotel', 'tx_epproducts_domain_model_country', 'country', 'hotel.country = country.uid') + ->leftJoin('hotel', 'tx_epproducts_domain_model_region', 'region', 'hotel.region = region.uid') + ->where('hotel.uid = :hotelUid') + ->andWhere('hotel.show_as_teaser = 1') + ->setParameter('hotelUid', $hotelUid) + ; + + return $qb->execute()->fetchAssociative(); + } + + /** + * @param int $productUid + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getListByProduct($productUid) + { + $qb = $this->getQueryBuilder(false); + $qb + ->andWhere('date.product = :productUid') + ->setParameter('productUid', $productUid) + ; + + return $qb->execute()->fetchAllAssociative(); + } + + /** + * @param array $criteria + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getListByCriteria(array $criteria) + { + $optionsResolver = new OptionsResolver(); + $optionsResolver->setDefined(['regionUid', 'countryUid', 'conceptUid', 'productUid', 'category', 'type']); + $resolvedCriteria = $optionsResolver->resolve($criteria); + + $qb = $this->getQueryBuilder(); + if ($resolvedCriteria['regionUid']) { + $qb + ->andWhere('date.region = :regionUid') + ->setParameter('regionUid', $resolvedCriteria['regionUid']) + ; + } + if ($resolvedCriteria['countryUid']) { + $qb + ->andWhere('date.country = :countryUid') + ->setParameter('countryUid', $resolvedCriteria['countryUid']) + ; + } + if ($resolvedCriteria['conceptUid']) { + $qb + ->andWhere('date.concept = :conceptUid') + ->setParameter('conceptUid', $resolvedCriteria['conceptUid']) + ; + } + if ($resolvedCriteria['productUid']) { + $qb + ->andWhere('date.product = :productUid') + ->setParameter('productUid', $resolvedCriteria['productUid']) + ; + } + if ($resolvedCriteria['category']) { + $qb + ->andWhere('date.hotel_category = :category') + ->setParameter('category', $resolvedCriteria['category']) + ; + } + if ($resolvedCriteria['type']) { + $qb + ->andWhere('date.hotel_type = :type') + ->setParameter('type', $resolvedCriteria['type']) + ; + } + + return $qb->execute()->fetchAllAssociative(); + } + + /** + * @param array $criteria + * @param array $excludedHotelUids + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getDateIndependentListByCriteria(array $criteria, array $excludedHotelUids = []) + { + $optionsResolver = new OptionsResolver(); + $optionsResolver->setDefined(['regionUid', 'countryUid', 'conceptUid', 'productUid', 'category', 'type']); + $resolvedCriteria = $optionsResolver->resolve($criteria); + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'hotel.uid as hotelUid', 'hotel.name as hotelName', 'hotel.teaser as hotelTeaser', + 'hotel.category as hotelCategory', 'hotel.detail_page as hotelDetailPage', + 'country.code as countryCode', 'country.name as country', + 'region.name as region' + ) + ->from('tx_epproducts_domain_model_hotel', 'hotel') + ->leftJoin('hotel', 'tx_epproducts_domain_model_country', 'country', 'hotel.country = country.uid') + ->leftJoin('hotel', 'tx_epproducts_domain_model_region', 'region', 'hotel.region = region.uid') + ->where('hotel.show_as_teaser = 1') + ->add('orderBy', 'FIELD(hotel.type, ' . Hotel::TYPE_SPORTS_CLUB . ') DESC') + ->addOrderBy('hotel.name', 'ASC') + ; + + if ($excludedHotelUids) { + $qb + ->andWhere('hotel.uid NOT IN (:excludedHotelUids)') + ->setParameter('excludedHotelUids', $excludedHotelUids, Connection::PARAM_INT_ARRAY) + ; + } + if ($resolvedCriteria['productUid']) { + $qb + ->innerJoin('hotel', 'tx_epproducts_product_hotel_mm', 'mm', 'hotel.uid = mm.uid_foreign') + ->andWhere('mm.uid_local = :product') + ->setParameter('product', $resolvedCriteria['productUid']) + ; + } + if ($resolvedCriteria['countryUid']) { + $qb + ->andWhere('hotel.country = :country') + ->setParameter('country', $resolvedCriteria['countryUid']) + ; + } + if ($resolvedCriteria['regionUid']) { + $qb + ->andWhere('hotel.region = :region') + ->setParameter('region', $resolvedCriteria['regionUid']) + ; + } + if ($resolvedCriteria['category']) { + $qb + ->andWhere('hotel.category = :category') + ->setParameter('category', $resolvedCriteria['category']) + ; + } + if ($resolvedCriteria['type']) { + $qb + ->andWhere('hotel.type = :type') + ->setParameter('type', $resolvedCriteria['type']) + ; + } + + return $qb->execute()->fetchAllAssociative(); + } + + /** + * @param bool $excludeNonSearchable + * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder + * @throws \Doctrine\DBAL\DBALException + */ + protected function getQueryBuilder($excludeNonSearchable = true) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.hotel as hotelUid', 'date.hotel_name as hotelName', 'date.hotel_teaser as hotelTeaser', + 'date.hotel_category as hotelCategory', 'date.hotel_detail_page as hotelDetailPage', + 'date.date_start as dateStart', 'date.date_end as dateEnd', 'date.earlybird as earlybird', + 'date.new as new', 'date.low_contingent as lowContingent', 'date.board as board', + 'date.product_fact as productFact', 'date.hotel_fact as hotelFact', 'date.region_fact as regionFact', + 'date.services_included as servicesIncluded', 'date.min_price as minPrice', + 'date.pseudo_price as pseudoPrice', 'date.nights as nights', 'date.available as available', + 'date.country as country', 'date.country_code as countryCode', 'date.region as region' + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->leftJoin('date', 'tx_epproducts_product_region_mm', 'mm', 'mm.uid_local = date.product') + ->addGroupBy('date.hotel') + ->addGroupBy('date.date_start') + ->addGroupBy('date.nights') + ->add('orderBy', 'FIELD(date.hotel_type, ' . Hotel::TYPE_SPORTS_CLUB . ') DESC') + ; + + if ($excludeNonSearchable) { + $qb->where('date.product_searchable = 1'); + } + + return $qb; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + public function getMapList() + { + $query = $this->createQuery(); + $query->matching( + $query->logicalAnd( + $query->greaterThan('latitude', 0), + $query->greaterThan('longitude', 0) + ) + ); + + return $query->execute(); + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/InfoboxRepository.php b/packages/ep_products/Classes/Domain/Repository/InfoboxRepository.php new file mode 100644 index 0000000..dbe54fe --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/InfoboxRepository.php @@ -0,0 +1,21 @@ +createQuery(); + $query->getQuerySettings()->setRespectStoragePage(false); + + $result = $query + ->matching($query->equals('defaultText', true)) + ->execute() + ; + + return $result->getFirst(); + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/MatchCodeRepository.php b/packages/ep_products/Classes/Domain/Repository/MatchCodeRepository.php new file mode 100644 index 0000000..a5a4427 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/MatchCodeRepository.php @@ -0,0 +1,31 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class MatchCodeRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Repository/ProductRepository.php b/packages/ep_products/Classes/Domain/Repository/ProductRepository.php new file mode 100644 index 0000000..9a1007a --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/ProductRepository.php @@ -0,0 +1,414 @@ +getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.concept as conceptUid', 'date.concept_name as conceptName', + 'date.concept_code as conceptCode', 'con.popup_text as conceptPopupText', + 'date.product as productUid', 'date.product_name as productName', + 'date.product_detail_page as productDetailPage', 'date.product_feature as productFeature', + 'date.country as countryUid','date.country_name as countryName', 'date.country_code as countryCode', + 'date.region as regionUid', 'date.region_name as regionName', 'date.region_feature as regionFeature', + 'date.date_start as dateStart', 'date.date_end as dateEnd', + 'date.hotel as hotelUid', 'date.hotel_name as hotelName', 'date.hotel_category as hotelCategory', + 'date.earlybird as earlybird', 'date.new as new', 'date.low_contingent as lowContingent', + 'date.board as board', 'date.product_fact as productFact', 'date.hotel_fact as hotelFact', + 'date.region_fact as regionFact', 'date.services_included as servicesIncluded', + 'date.skipass_included as skiPassIncluded', 'date.min_price as minPrice', 'date.nights as nights', + 'date.available as available', 'date.pseudo_price as pseudoPrice' + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->leftJoin('date', 'tx_epproducts_domain_model_concept', 'con', 'date.concept = con.uid') + ->where($qb->expr()->in( + 'date.product', + $qb->createNamedParameter($productUids, Connection::PARAM_INT_ARRAY)) + ) + ->andWhere($qb->expr()->in( + 'date.hotel', + $qb->createNamedParameter($hotelUids, Connection::PARAM_INT_ARRAY)) + ) + ->andWhere($qb->expr()->eq('date.product_searchable', 1)) + ->orderBy('date.concept_sorting') + ->addOrderBy('date.product_name') + ->addOrderBy('date.min_price') + ->addOrderBy('date.date_start') + ->addOrderBy('date.hotel_name') + ; + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function getSearchResult(array $filterSettings, array $excludedConceptUids = []): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.concept as conceptUid', 'date.concept_name as conceptName', + 'date.concept_code as conceptCode', 'date.concept_sorting as conceptSorting', + 'con.popup_text as conceptPopupText', + 'date.product as productUid', 'date.product_name as productName', + 'date.product_detail_page as productDetailPage', 'date.product_feature as productFeature', + 'date.daytrip as daytrip', 'date.country as countryUid','date.country_name as countryName', + 'date.country_code as countryCode', 'date.region as regionUid', 'date.region_name as regionName', + 'date.region_feature as regionFeature', 'date.date_start as dateStart', 'date.date_end as dateEnd', + 'date.hotel as hotelUid', 'date.hotel_name as hotelName', 'date.hotel_category as hotelCategory', + 'date.hotel_type as hotelType', 'date.season as season', + 'date.earlybird as earlybird', 'date.new as new', 'date.low_contingent as lowContingent', + 'date.board as board', 'date.board_bus_pro_id as boardUid', + 'date.product_fact as productFact', 'date.hotel_fact as hotelFact', 'date.region_fact as regionFact', + 'date.services_included as servicesIncluded', 'date.skipass_included as skiPassIncluded', + 'date.bus_available as busAvailable', 'date.new as new', + 'date.min_price as minPrice', 'date.nights as nights', 'date.available as available', + 'date.pseudo_price as pseudoPrice', 'date.hide_booking_button as hideBookingButton' + ) + // The GROUP BY below collapses many room rows per group; an arbitrary + // room.type would hide most types from the filter facet. + ->addSelectLiteral('GROUP_CONCAT(DISTINCT room.type) as roomTypes') + ->from('tx_epproducts_domain_model_date', 'date') + ->leftJoin('date', 'tx_epproducts_domain_model_concept', 'con', 'date.concept = con.uid') + ->leftJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid') + ->where($qb->expr()->andX( + $qb->expr()->eq('date.product_searchable', 1), + $qb->expr()->gt('date.available', 0), + 'date.date_start >= NOW()' + )) + ->groupBy('date.product') + ->addGroupBy('date.hotel') + ->addGroupBy('date.date_start') + ->addGroupBy('date.nights') + ->orderBy('date.concept_sorting') + ->addOrderBy('important', 'DESC') + ->addOrderBy('date.product_name') + ->addOrderBy('date.min_price') + ->addOrderBy('date.date_start') + ->addOrderBy('date.hotel_name') + ; + + if (count($excludedConceptUids) > 0) { + $qb->andWhere($qb->expr()->notIn( + 'date.concept', + $qb->createNamedParameter($excludedConceptUids, Connection::PARAM_INT_ARRAY)) + ); + } + + if ($filterSettings['productsPid']) { + $productsPid = $filterSettings['productsPid']; + $qb->andWhere($qb->expr()->in( + 'date.product_pid', + $qb->createNamedParameter($productsPid, Connection::PARAM_INT_ARRAY)) + ); + } + + $this->addFiltersettingsConditions($qb, $filterSettings); + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function getTeasers(array $filterSettings, array $excludedConceptUids = [], int $limit = 0): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.product as productUid', 'date.product_name as productName', + 'date.product_detail_page as productDetailPage', 'date.product_feature as productFeature', + 'date.product_teaser as productTeaser', 'date.product_fact as productFact', + 'date.product_slug as productSlug', + 'date.hotel as hotelUid', 'date.hotel_fact as hotelFact', 'date.hotel_name as hotelName', + 'date.hotel_category as hotelCategory', 'date.region_fact as regionFact', + 'date.region_name as regionName', 'date.region_feature as regionFeature', + 'date.country_name as countryName', 'date.country_code as countryCode', + 'date.date_start as dateStart', 'date.date_end as dateEnd', 'date.uid as dateUid', + 'date.min_price as minPrice', 'date.pseudo_price as pseudoPrice', + 'date.nights as nights', 'date.available as available', 'date.season as season', + 'date.skipass_included as skiPassIncluded', 'date.bus_included as busIncluded', + 'date.earlybird', 'date.new', 'date.daytrip', 'date.hide_booking_button as hideBookingButton', + 'date.low_contingent as lowContingent', 'date.concept as conceptUid', + 'date.concept_code as conceptCode', 'date.concept_name as conceptName', + 'con.popup_text as conceptPopupText', 'date.board' + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->leftJoin('date', 'tx_epproducts_domain_model_concept', 'con', 'date.concept = con.uid') + ->where('date.date_start >= NOW()') + ->groupBy('date.product') + ->addGroupBy('date.hotel') + ->addGroupBy('date.date_start') + ->addGroupBy('date.nights') + ->having('date.available > 0') + ->orderBy('date.important', 'DESC') + ->addOrderBy('date.new', 'DESC') + ->addOrderBy('date.concept_sorting') + ->addOrderBy('date.date_start') + ; + + if ($filterSettings['productsPid']) { + $productsPid = (array) $filterSettings['productsPid']; + $qb->andWhere($qb->expr()->in( + 'date.product_pid', + $qb->createNamedParameter($productsPid, Connection::PARAM_INT_ARRAY)) + ); + } + + if ($limit > 0) { + $qb->setMaxResults($limit); + } + + if (empty($filterSettings['productUid'])) { + $qb->andWhere($qb->expr()->eq('date.product_searchable', 1)); + } else { + $qb->andWhere($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($filterSettings['productUid'], Connection::PARAM_INT)) + ); + } + + $this->addDateFilterConditions($qb, $filterSettings); + $this->addHotelFilterConditions($qb, $filterSettings); + $this->addCountryRegionAndCityFilterConditions($qb, $filterSettings); + $this->addConceptFilterConditions($qb, $filterSettings); + $this->addTransportationFilterConditions($qb, $filterSettings); + + if (count($excludedConceptUids) > 0) { + $qb->andWhere($qb->expr()->notIn( + 'concept', + $qb->createNamedParameter($excludedConceptUids, Connection::PARAM_INT_ARRAY)) + ); + } + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function getPricetable(Product $product, Hotel $hotel, array $filterSettings, Date $date = null): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.uid as dateUid', 'date.date_start as dateStart', 'date.date_end as dateEnd', + 'date.bus_pro_id as dateBusProId', 'date.hotel as hotelUid', 'date.hotel_bus_pro_id as hotelBusProId', + 'date.bus_included as dateBusIncluded', 'date.skipass_included as dateSkiPassIncluded', + 'date.group_travel as dateGroupTravel', + 'date.season as season', 'date.product_detail_page as productDetailPage', + 'room.uid as roomUid', 'room.bus_pro_id as roomBusProId', 'room.name as roomName', + 'room.price as roomPrice', 'date.discount as roomDiscount', 'date.bus_price as busPrice', + 'room.services as roomOptionalServices', 'room.nights as roomNights', 'room.available as roomAvailable', + 'room.status as roomStatus' + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'date.uid = room.date') + ->orderBy('room.pax', 'ASC') + ->addOrderBy('room.price', 'ASC') + ->addOrderBy('room.available', 'DESC') + ; + + $qb + ->where('date.date_start >= NOW()') + ->andWhere($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($product->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->eq( + 'date.hotel', + $qb->createNamedParameter($hotel->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->neq( + 'room.status', + $qb->createNamedParameter(Room::ROOM_STATUS_FULLY_BOOKED, Connection::PARAM_INT)) + ) + ; + + if ($filterSettings['productsPid']) { + $productsPid = (array) $filterSettings['productsPid']; + $qb->andWhere($qb->expr()->in( + 'date.product_pid', + $qb->createNamedParameter($productsPid, Connection::PARAM_INT_ARRAY)) + ); + } + + if ($date !== null) { + $qb->andWhere($qb->expr()->eq( + 'date.uid', + $qb->createNamedParameter($date->getUid(), Connection::PARAM_INT)) + ); + } + + $this->addFiltersettingsConditions($qb, $filterSettings); + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function getAvailableDates(Product $product, Hotel $hotel, array $filterSettings): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.uid as dateUid', 'date.date_start as dateStart', 'date.date_end as dateEnd', + 'date.season as season', 'date.min_price as dateMinPrice', 'date.pseudo_price as datePseudoPrice', + 'date.discount as dateDiscount', 'date.bus_price as busPrice', 'date.nights as dateNights', + 'date.bus_included as dateBusIncluded', 'date.services_included as dateServicesIncluded', + 'date.services_general as dateServicesGeneral', 'date.skipass_included as dateSkiPassIncluded', + 'date.group_travel as dateGroupTravel', + 'date.bus_pro_id as dateBusProId', 'date.hotel_bus_pro_id as hotelBusProId', + 'date.available as available', 'date.product_detail_page as productDetailPage', + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid') + ->orderBy('date.date_start', 'ASC') + ->addOrderBy('date.min_price', 'ASC') + ->where('date.date_start >= NOW()') + ->andWhere($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($product->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->eq( + 'date.hotel', + $qb->createNamedParameter($hotel->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->neq( + 'room.status', + $qb->createNamedParameter(Room::ROOM_STATUS_FULLY_BOOKED, Connection::PARAM_INT)) + ) + ->groupBy('date.date_start') + ; + + if ($filterSettings['productsPid']) { + $productsPid = (array) $filterSettings['productsPid']; + $qb->andWhere($qb->expr()->in( + 'date.product_pid', + $qb->createNamedParameter($productsPid, Connection::PARAM_INT_ARRAY)) + ); + } + + $this->addFiltersettingsConditions($qb, $filterSettings, true); + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function hasDates(Product $product): bool + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('date.uid') + ->from('tx_epproducts_domain_model_date', 'date') + ->where($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($product->getUid(), Connection::PARAM_INT)) + ) + ->setMaxResults(1) + ->execute() + ->fetchOne() + ; + + return $result !== false; + } + + public function getDatesForExport(Product $product, Hotel $hotel): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.bus_pro_id as dateBusProId', 'date.date_start as dateStart', 'date.date_end as dateEnd', + 'date.min_price as dateMinPrice', 'date.discount as dateDiscount', 'date.bus_price as busPrice', + 'date.nights as dateNights', 'date.bus_included as dateBusIncluded', 'date.bus_price as busPrice', + 'date.services_included as dateServicesIncluded', 'date.services_general as dateServicesGeneral', + 'date.skipass_included as dateSkiPassIncluded', 'date.bus_pro_id as dateBusProId', + 'date.discount as roomDiscount', 'date.hotel_bus_pro_id as hotelBusProId', 'date.pickups as pickups', + 'room.uid as roomUid', 'room.bus_pro_id as roomBusProId', 'room.code as roomCode', + 'room.name as roomName', 'room.price as roomPrice', 'room.pax as roomPax', + 'room.services as roomOptionalServices', 'room.nights as roomNights', 'room.available as roomAvailable' + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid') + ->orderBy('date.date_start', 'ASC') + ->addOrderBy('date.min_price', 'ASC') + ->where('date.date_start >= NOW()') + ->andWhere($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($product->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->eq( + 'date.hotel', + $qb->createNamedParameter($hotel->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->neq( + 'room.status', + $qb->createNamedParameter(Room::ROOM_STATUS_FULLY_BOOKED, Connection::PARAM_INT)) + ) + ; + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + + public function getEventPriceTable(Product $product): array + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $qb + ->select( + 'date.bus_pro_id as dateBusProId', 'date.hotel_bus_pro_id as hotelBusProId', + 'date.date_end as dateEnd', 'date.hotel as hotelUid', 'date.hotel_name as hotelName', + 'date.hotel_header_title as hotelTitle', 'date.hotel_category as hotelCategory', + 'room.name as roomName', 'room.pax as roomPax', 'room.price as roomPrice', + 'room.available as available', + 'hotel.detail_page as hotelDetailPageUid', 'hotel.short_name as hotelShortName', + 'hotel.show_as_teaser as hotelShowAsTeaser', + 'hotel_mapping.detail_page as hotelMappingDetailPageUid', + ) + ->from('tx_epproducts_domain_model_date', 'date') + ->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'room.date = date.uid') + ->innerJoin('date', 'tx_epproducts_domain_model_hotel', 'hotel', 'hotel.uid = date.hotel') + ->leftJoin('hotel', 'tx_epproducts_domain_model_hotel_mapping', 'hotel_mapping', 'hotel_mapping.product = date.product AND hotel_mapping.hotel = date.hotel') + ->where($qb->expr()->eq( + 'date.product', + $qb->createNamedParameter($product->getUid(), Connection::PARAM_INT)) + ) + ->andWhere($qb->expr()->neq( + 'room.status', + $qb->createNamedParameter(Room::ROOM_STATUS_FULLY_BOOKED, Connection::PARAM_INT)) + ) + ->orderBy('room.available', 'DESC') + ->addOrderBy('room.name', 'ASC') + ; + + return $qb + ->execute() + ->fetchAllAssociative() + ; + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/RegionRepository.php b/packages/ep_products/Classes/Domain/Repository/RegionRepository.php new file mode 100644 index 0000000..5f3baa6 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/RegionRepository.php @@ -0,0 +1,56 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class RegionRepository extends AbstractRepository +{ + public function getTeasersByCountryAndSeason(int $countryUid, string $season = 'w', int $storagePid = 0) + { + if ($season === null) { + $season = AbstractDestination::SEASON_WINTER; + } + $query = $this->createQuery(); + $constraints = [ + $query->equals('country', $countryUid), + $query->equals('season', $season), + $query->equals('excludeFromTeasergroups', 0) + ]; + + if (0 < $storagePid) { + $constraints[] = $query->equals('pid', $storagePid); + } + + return $query + ->matching($query->logicalAnd($constraints)) + ->execute() + ; + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/ResellerRepository.php b/packages/ep_products/Classes/Domain/Repository/ResellerRepository.php new file mode 100644 index 0000000..eb02598 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/ResellerRepository.php @@ -0,0 +1,31 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class ResellerRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Repository/RoomRepository.php b/packages/ep_products/Classes/Domain/Repository/RoomRepository.php new file mode 100644 index 0000000..90a8e36 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/RoomRepository.php @@ -0,0 +1,31 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class RoomRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Repository/SnowreportRepository.php b/packages/ep_products/Classes/Domain/Repository/SnowreportRepository.php new file mode 100644 index 0000000..bde5d50 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/SnowreportRepository.php @@ -0,0 +1,71 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Database\Connection; + +class SnowreportRepository extends AbstractRepository +{ + + /** + * @param array $regionUids + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getSnowreportsForRegions(array $regionUids = null) + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $query = $qb + ->select( + 'snowreport.*', + 'region.name as regionName', 'region.uid as regionUid', 'region.detail_page as regionDetailPage', + 'country.name as countryName', 'country.uid as countryUid', 'country.code as countryCode', + 'country.detail_page as countryDetailPage', + 'season_start as seasonStart', 'season_end as seasonEnd' + ) + ->addSelectLiteral('(UNIX_TIMESTAMP() >= snowreport.season_start AND UNIX_TIMESTAMP() <= snowreport.season_end) as inSeason') + ->from('tx_epproducts_domain_model_snowreport', 'snowreport') + ->innerJoin('snowreport', 'tx_epproducts_domain_model_region', 'region', 'snowreport.uid = region.snowreport') + ->innerJoin('region', 'tx_epproducts_domain_model_country', 'country', 'region.country = country.uid') + ->where('region.snowreport > 0') + ->groupBy('snowreport.vendor_uid') + ->orderBy('region.name') + ; + + if ($regionUids) { + $qb + ->andWhere('region.uid IN (:regionUids)') + ->setParameter('regionUids', $regionUids, Connection::PARAM_INT_ARRAY) + ; + } + + return $query->execute()->fetchAllAssociative(); + } + +} diff --git a/packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php b/packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php new file mode 100644 index 0000000..9e64a20 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php @@ -0,0 +1,52 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class TeammemberRepository extends AbstractRepository +{ + /** + * @var array + */ + protected $defaultOrderings = [ + 'sorting' => 'ASC', + ]; + + /** + * @param array $categories + * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + public function findByCategories(array $categories = []) + { + $qb = $this->createQuery(); + + $qb->matching($qb->in('categories.uid', $categories)); + + return $qb->execute(); + } +} diff --git a/packages/ep_products/Classes/Domain/Repository/TravelinfoRepository.php b/packages/ep_products/Classes/Domain/Repository/TravelinfoRepository.php new file mode 100644 index 0000000..f62f188 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Repository/TravelinfoRepository.php @@ -0,0 +1,6 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class WebcamRepository extends AbstractRepository +{} diff --git a/packages/ep_products/Classes/Domain/Serialization/City.php b/packages/ep_products/Classes/Domain/Serialization/City.php new file mode 100644 index 0000000..b75f87d --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/City.php @@ -0,0 +1,57 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Fact; +use EP\EpProducts\Utility\SerializationUtility; + +class City +{ + /** + * @param \EP\EpProducts\Domain\Model\City $entity + * @return array + */ + public static function fromEntity(\EP\EpProducts\Domain\Model\City $entity) + { + $city = []; + + $city['@id'] = $entity->getUid(); + $city['name'] = $entity->getName(); + $city['description'] = SerializationUtility::sanitizeText($entity->getDescription()); + $city['party'] = SerializationUtility::sanitizeText($entity->getParty()); + $city['leisure'] = SerializationUtility::sanitizeText($entity->getLeisure()); + $city['facts'] = []; + $city['facts']['fact'] = array_map(function ($fact) { + /** @var Fact $fact */ + return htmlspecialchars($fact->getName()); + }, $entity->getFacts()->toArray()); + + return $city; + } + +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Date.php b/packages/ep_products/Classes/Domain/Serialization/Date.php new file mode 100644 index 0000000..05270a2 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Date.php @@ -0,0 +1,63 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Date +{ + /** + * @param array $array + * @return array + */ + public static function fromArray(array $array) + { + $date = [ + 'servicesincluded' => [], + 'servicesoptional' => [], + 'rooms' => [], + ]; + + $date['@id'] = $array['dateBusProId']; + $date['from'] = $array['dateStart']; + $date['to'] = $array['dateEnd']; + $date['minprice'] = (float) $array['dateMinPrice']; + $date['nights'] = $array['dateNights']; + $date['skipassincluded'] = $array['dateSkiPassIncluded']; + $date['busincluded'] = $array['dateBusIncluded']; + $date['servicesincluded']['service'] = unserialize($array['dateServicesIncluded']); + $servicesGeneral = unserialize($array['dateServicesGeneral']); + $date['servicesoptional'] = Services::fromArray($servicesGeneral); + $date['pickups'] = []; + foreach (unserialize($array['pickups']) as $row) + { + $date['pickups']['pickup'][] = $row; + } + + return $date; + } + +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Hotel.php b/packages/ep_products/Classes/Domain/Serialization/Hotel.php new file mode 100644 index 0000000..9de9509 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Hotel.php @@ -0,0 +1,56 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Hotel +{ + /** + * @param \EP\EpProducts\Domain\Model\Hotel $entity + * @return array + */ + public static function fromEntity(\EP\EpProducts\Domain\Model\Hotel $entity) + { + $hotel = [ + 'dates' => [], + 'images' => [], + ]; + + $hotel['@id'] = $entity->getUid(); + $hotel['@code'] = $entity->getCode(); + $hotel['name'] = $entity->getName(); + $hotel['category'] = \EP\EpProducts\Domain\Model\Hotel::$categoryLabels[$entity->getCategory()]; + $hotel['type'] = \EP\EpProducts\Domain\Model\Hotel::$typeLabels[$entity->getType()]; + $hotel['description'] = SerializationUtility::sanitizeText($entity->getDescription()); + $hotel['roomtypes'] = SerializationUtility::sanitizeText($entity->getRoomTypes()); + $hotel['features'] = SerializationUtility::sanitizeText($entity->getFeatures()); + $hotel['images']['image'] = SerializationUtility::preprocessImages($entity->getResellerImages()); + + return $hotel; + } +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Product.php b/packages/ep_products/Classes/Domain/Serialization/Product.php new file mode 100644 index 0000000..864b022 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Product.php @@ -0,0 +1,104 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Fact; +use EP\EpProducts\Utility\SerializationUtility; +use TYPO3\CMS\Core\Site\SiteFinder; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class Product +{ + /** + * @param \EP\EpProducts\Domain\Model\Product $entity + * @param array $imageProcessingInstructions + * @param array $queryData + * @return array + */ + public static function fromEntity + ( + \EP\EpProducts\Domain\Model\Product $entity, + array $imageProcessingInstructions = null, + array $queryData = [] + ) + { + $product = [ + 'concept' => [], + 'facts' => [], + 'hotels' => [], + 'images' => [], + ]; + + $product['@id'] = $entity->getBusProId(); + $product['@code'] = $entity->getCode(); + $product['name'] = preg_replace('/[\r\n\s]+/', ' ', $entity->getName()) . ' - ' . $entity->getRegion()->getName(); + + //TODO: Don't hardcode concept uids here, make them configurable + $conceptUid = $entity->getConcept()->getUid(); + if ($conceptUid !== 3 && $conceptUid !== 4) { + $product['name'] .= ' (' . $entity->getConcept()->getName() . ')'; + } + + $product['facts']['fact'] = array_map(function ($fact) { + /** @var Fact $fact */ + return htmlspecialchars($fact->getName()); + }, $entity->getFacts()->toArray()); + + $product['skipass'] = SerializationUtility::sanitizeText($entity->getSkiPassDescription()); + $product['concept']['name'] = $entity->getConcept()->getName(); + $product['concept']['description'] = SerializationUtility::sanitizeText($entity->getConcept()->getDescription()); + $product['board'] = SerializationUtility::sanitizeText($entity->getBoardDescription()); + $product['programme'] = SerializationUtility::sanitizeText($entity->getProgrammeDescription()); + $product['description'] = SerializationUtility::sanitizeText($entity->getTeaser()); + $product['images']['image'] = SerializationUtility::preprocessImages($entity->getResellerImages(), $imageProcessingInstructions); + $product['video'] = $entity->getVideo(); + $product['journey'] = null; + + if ($entity->getJourney()) { + $product['journey'] = [ + 'byBus' => SerializationUtility::sanitizeText($entity->getJourney()->getByBus()), + 'byCar' => SerializationUtility::sanitizeText($entity->getJourney()->getByCar()), + ]; + } + + if ($entity->getExternalLink()) { + $product['link'] = $entity->getExternalLink(); + } else { + /** @var SiteFinder $siteFinder */ + $siteFinder = GeneralUtility::makeInstance(SiteFinder::class); + $site = $siteFinder->getSiteByIdentifier('ep-reisen'); + $product['link'] = $site->getRouter()->generateUri($entity->getDetailPage(), $queryData); + } + + $product['region'] = Region::fromEntity($entity->getRegion()); + $product['city'] = City::fromEntity($entity->getCity()); + + return $product; + } + +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Region.php b/packages/ep_products/Classes/Domain/Serialization/Region.php new file mode 100644 index 0000000..312b74c --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Region.php @@ -0,0 +1,61 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Fact; +use EP\EpProducts\Utility\SerializationUtility; + +class Region +{ + /** + * @param \EP\EpProducts\Domain\Model\Region $entity + * @return array + */ + public static function fromEntity(\EP\EpProducts\Domain\Model\Region $entity) + { + $region = []; + + $region['@id'] = $entity->getUid(); + $region['name'] = $entity->getName(); + $region['description'] = SerializationUtility::sanitizeText($entity->getSkiArea()); + $region['descriptionExtended'] = SerializationUtility::sanitizeText($entity->getSkiAreaExtended()); + $region['piste'] = $entity->getLength(); + $region['height'] = $entity->getHeight(); + $region['lifts'] = $entity->getLifts(); + $region['skipass'] = SerializationUtility::sanitizeText($entity->getSkiPass()); + $region['news'] = SerializationUtility::sanitizeText($entity->getNews()); + $region['facts'] = []; + $region['facts']['fact'] = array_map(function ($fact) { + /** @var Fact $fact */ + return htmlspecialchars($fact->getName()); + }, $entity->getFacts()->toArray()); + + return $region; + } + +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Room.php b/packages/ep_products/Classes/Domain/Serialization/Room.php new file mode 100644 index 0000000..e6d6025 --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Room.php @@ -0,0 +1,53 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Room +{ + /** + * @param array $array + * @return array + */ + public static function fromArray(array $array) + { + $room = []; + + $room['@id'] = $array['roomBusProId']; + $room['@pax'] = $array['roomPax']; + $room['@code'] = $array['roomCode']; + $room['name'] = $array['roomName']; + $room['price'] = (float) $array['roomPrice']; + $room['busprice'] = (float) $array['busPrice']; + $room['nights'] = $array['roomNights']; + $room['available'] = $array['roomAvailable']; + + $servicesOptional = unserialize($array['roomOptionalServices']); + $room['servicesoptional'] = Services::fromArray($servicesOptional); + + return $room; + } +} diff --git a/packages/ep_products/Classes/Domain/Serialization/Services.php b/packages/ep_products/Classes/Domain/Serialization/Services.php new file mode 100644 index 0000000..2ee2a8f --- /dev/null +++ b/packages/ep_products/Classes/Domain/Serialization/Services.php @@ -0,0 +1,73 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class Services +{ + /** + * @var array + */ + protected static $categoryLabels = [ + 'BUS' => 'Anreise', + 'VPF' => 'Verpflegung', + 'SPA' => 'Skipass', + 'KUR' => 'Kurse', + 'VER' => 'Verleih', + 'SON' => 'Sonstiges', + ]; + + /** + * @param array $array + * @return array + */ + public static function fromArray(array $array) + { + $services = [ + 'category' => [], + ]; + + foreach ($array as $categoryId => $categoryData) + { + $category = [ + 'service' => [], + '@label' => self::$categoryLabels[$categoryId], + ]; + foreach ($categoryData as $row) + { + $service = [ + '@id' => $row['idBusPro'], + 'label' => $row['label'], + 'price' => $row['price'], + ]; + $category['service'][] = $service; + } + $services['category'][] = $category; + } + + return $services; + } +} diff --git a/packages/ep_products/Classes/EpTeam/ApiClient.php b/packages/ep_products/Classes/EpTeam/ApiClient.php new file mode 100644 index 0000000..b348592 --- /dev/null +++ b/packages/ep_products/Classes/EpTeam/ApiClient.php @@ -0,0 +1,244 @@ +request('POST', $uri, [ + 'json' => $payload, + ]); + + $statusCode = $this->readStatusCode($response, 'POST', $uri); + + if (false === in_array($statusCode, self::ACCEPTED_STATUS_CODES, true)) { + $message = sprintf( + 'EP Team API returned unexpected status %d for POST %s: %s', + $statusCode, + $uri, + $this->readErrorBody($response) + ); + $this->logWarning($message); + + throw new ApiException($message, $statusCode); + } + + $this->logAcceptedSubmission($response, $payload); + } + + /** + * @throws ApiException + */ + private function request(string $method, string $uri, array $options): ResponseInterface + { + try { + return $this->getHttpClient()->request($method, $uri, $options); + } catch (TransportExceptionInterface $e) { + $message = sprintf('EP Team API transport error for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + + throw new ApiException($message, 0, $e); + } + } + + /** + * Reading the status completes the transfer, so this is also what turns the lazy response + * returned by the client into a finished request. + * + * @throws ApiException + */ + private function readStatusCode(ResponseInterface $response, string $method, string $uri): int + { + try { + return $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + $message = sprintf('EP Team API transport error while reading status for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + + throw new ApiException($message, 0, $e); + } + } + + private function readErrorBody(ResponseInterface $response): string + { + try { + $body = trim($response->getContent(false)); + } catch (TransportExceptionInterface $e) { + return ''; + } + + if ('' === $body) { + return ''; + } + + if (mb_strlen($body) > self::ERROR_BODY_EXCERPT_LENGTH) { + $body = mb_substr($body, 0, self::ERROR_BODY_EXCERPT_LENGTH) . '…'; + } + + return $body; + } + + /** + * The submission uuid the endpoint returns is what makes a support question ("did our + * application arrive?") answerable from the TYPO3 log alone. Reading the body also drains + * the response, which returns the connection to the pool instead of cancelling the transfer. + * Deliberately without any of the submitted values: these payloads are personal data. + */ + private function logAcceptedSubmission(ResponseInterface $response, array $payload): void + { + try { + $data = $response->toArray(false); + } catch (\Throwable $e) { + // A body that is absent (204) or not JSON costs nothing here - the submission is + // stored either way, which the asserted status has already established. + return; + } + + if (null !== $this->logger) { + $this->logger->info('Application form submission accepted by the EP Team API.', [ + 'form' => $payload['form'] ?? null, + 'pageUid' => $payload['pageUid'] ?? null, + 'uuid' => $data['uuid'] ?? null, + ]); + } + } + + /** + * @throws ApiException + */ + private function getHttpClient(): HttpClientInterface + { + if (null !== static::$httpClient) { + return static::$httpClient; + } + + $config = $this->getConfiguration(); + $baseUrl = trim((string)($config['epTeamApiBaseUrl'] ?? '')); + $apiKey = trim((string)($config['epTeamApiKey'] ?? '')); + + if ('' === $baseUrl) { + throw new ApiException('EP Team API base URL is not configured.'); + } + if ('' === $apiKey) { + throw new ApiException('EP Team API key is not configured.'); + } + if (0 !== strpos($baseUrl, 'https://')) { + // The application answers an http request with a 301 to the https URL, and this + // client does not follow redirects, so a plain http base URL fails every submission. + $this->logWarning(sprintf('EP Team API base URL is not https (%s); submissions will fail on the redirect.', $baseUrl)); + } + + $options = [ + 'headers' => [ + 'X-Api-Key' => $apiKey, + ], + ]; + + // Without these the client falls back to PHP's default_socket_timeout (commonly 60s), + // so a stalled API pins a PHP worker for a minute per request - and this call happens + // inside the visitor's form submission, with the visitor waiting for it. The defaults + // live in code rather than in ext_conf_template.txt: ExtensionConfiguration::get() + // returns the stored configuration verbatim and only syncs the template when an + // extension has no configuration at all, so a newly added template key is absent until + // someone saves extension configuration by hand. Falling back to 0 there would silently + // mean "no timeout" - the opposite of what this guard is for. An explicit 0 still + // disables. + $timeout = (int)($config['epTeamApiTimeout'] ?? self::DEFAULT_TIMEOUT); + if ($timeout > 0) { + $options['timeout'] = $timeout; + } + $maxDuration = (int)($config['epTeamApiMaxDuration'] ?? self::DEFAULT_MAX_DURATION); + if ($maxDuration > 0) { + $options['max_duration'] = $maxDuration; + } + + return static::$httpClient = HttpClient::createForBaseUri(rtrim($baseUrl, '/') . '/', $options); + } + + private function getConfiguration(): array + { + if (null === $this->configuration) { + $this->configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); + } + + return $this->configuration; + } + + private function logError(string $message, array $context = []): void + { + if (null !== $this->logger) { + $this->logger->error($message, $context); + } + } + + private function logWarning(string $message, array $context = []): void + { + if (null !== $this->logger) { + $this->logger->warning($message, $context); + } + } +} diff --git a/packages/ep_products/Classes/EpTeam/ApiException.php b/packages/ep_products/Classes/EpTeam/ApiException.php new file mode 100644 index 0000000..e0d7d2c --- /dev/null +++ b/packages/ep_products/Classes/EpTeam/ApiException.php @@ -0,0 +1,7 @@ +readImportTimestamp('fileadmin/products_import.info'); + $contingentsDataUpdatedTimestamp = $timestampService->readImportTimestamp('fileadmin/contingents_import.info'); + + $event->getToolbarItem()->addSystemInformation( + 'Letzer Produktdaten-Import', + $productDataUpdatedTimestamp ? $productDataUpdatedTimestamp->format('d.m.y H:i:s') : '-', + 'ep-logo' + ); + $event->getToolbarItem()->addSystemInformation( + 'Letzer Kontingente Import', + $contingentsDataUpdatedTimestamp ? $contingentsDataUpdatedTimestamp->format('d.m.y H:i:s') : '-', + 'ep-logo' + ); + } +} diff --git a/packages/ep_products/Classes/Hooks/PageLayoutView.php b/packages/ep_products/Classes/Hooks/PageLayoutView.php new file mode 100644 index 0000000..43fbf4e --- /dev/null +++ b/packages/ep_products/Classes/Hooks/PageLayoutView.php @@ -0,0 +1,258 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Backend\View\PageLayoutView as CorePageLayoutView; +use TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface; +use TYPO3\CMS\Core\Service\FlexFormService; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class PageLayoutView implements PageLayoutViewDrawItemHookInterface +{ + + /** + * @param CorePageLayoutView $parentObject + * @param bool $drawItem + * @param string $headerContent + * @param string $itemContent + * @param array $row + */ + public function preProcess(CorePageLayoutView &$parentObject, &$drawItem, &$headerContent, &$itemContent, array &$row) { + // Country detail + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_country_detail') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $countryUid = $flexFormData['settings']['countryUid']; + $name = $this->getName('country', $countryUid); + $itemContent = 'Detailseite Land
' . $name; + } + + // Region teaser + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_region_teasergroup') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $countryUid = $flexFormData['settings']['countryUid']; + $name = $this->getName('country', $countryUid); + $itemContent = 'Teasergruppe Gebiete
' . $name; + } + + // Region detail + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_region_detail') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $regionUid = $flexFormData['settings']['regionUid']; + $name = $this->getName('region', $regionUid); + $itemContent = 'Detailseite Gebiet
' . $name; + } + + // City teaser + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_city_teasergroup') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $countryUid = $flexFormData['settings']['countryUid']; + $regionUid = $flexFormData['settings']['regionUid']; + $record = []; + if ((int) $regionUid > 0) { + $name = $this->getName('region', $regionUid); + } elseif ((int) $countryUid > 0) { + $name = $this->getName('country', $countryUid); + } + $itemContent = 'Teasergruppe Orte
'; + if (count($record) > 0) { + $itemContent .= $name; + } + } + + // City detail + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_city_detail') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $cityUid = $flexFormData['settings']['cityUid']; + $name = $this->getName('city', $cityUid); + $itemContent = 'Detailseite Ort
' . $name; + } + + // Hotel Teaser + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_hotel_teasergroup') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $countryUid = $flexFormData['settings']['countryUid']; + $regionUid = $flexFormData['settings']['reguionUid']; + $cityUid = $flexFormData['settings']['cityUid']; + $name = ''; + if ((int) $cityUid > 0) { + $name = $this->getName('city', $cityUid); + } elseif ((int) $regionUid > 0) { + $name = $this->getName('region', $regionUid); + } elseif ((int) $countryUid > 0) { + $name = $this->getName('country', $countryUid); + } + $itemContent = 'Teasergruppe Hotels
' . $name; + } + + // Hotel detail + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_hotel_detail') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $hotelUid = $flexFormData['settings']['hotelUid']; + $name = $this->getName('hotel', $hotelUid); + $itemContent = 'Detailseite Hotel
' . $name; + } + + // FAQ + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_faq') { + $flexFormData = $this->getFlexFormData($row); + $faqUids = GeneralUtility::trimExplode(',', $flexFormData['settings']['faqUids']); + $drawItem = false; + $itemContent = 'FAQ
' . count($faqUids) . ' Frage(n)'; + } + + // Product pricetable + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_product_pricetable') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $itemContent = 'Buchungstabelle
'; + $productUid = $flexFormData['settings']['productUid']; + $record = BackendUtility::getRecord('tx_epproducts_domain_model_product', $productUid); + $itemContent .= $record['name']; + $hotelUid = $flexFormData['settings']['hotelUid']; + $record = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $hotelUid); + $itemContent .= ', ' . $record['name']; + } + + // Product detail + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_product_detail') { + $flexFormData = $this->getFlexFormData($row); + $drawItem = false; + $itemContent = 'Produkt
'; + $productUid = $flexFormData['settings']['productUid']; + if ((int) $productUid === 0) { + $itemContent .= 'allgemein'; + } else { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_product', $productUid); + $itemContent .= $record['name_internal'] ? $record['name_internal'] : $record['name']; + $hotelUid = $flexFormData['settings']['hotelUid']; + if ((int) $hotelUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $hotelUid); + $itemContent .= ', ' . $record['name']; + } + } + } + + // Product teaser + if ($row['CType'] === 'list' && $row['list_type'] === 'epproducts_product_teasergroup') { + $flexFormData = $this->getFlexFormData($row); + $mode = $this->getMode($flexFormData); + $drawItem = false; + switch ($mode) + { + case 'teasergroup': + $itemContent = 'Teasergruppe Produkte
'; + $filterParts = []; + $countryUid = $flexFormData['settings']['countryUid']; + if ((int) $countryUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_country', $countryUid); + $filterParts[] = $record['name']; + } + $regionUid = $flexFormData['settings']['regionUid']; + if ((int) $regionUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_region', $regionUid); + $filterParts[] = $record['name']; + } + $conceptUid = $flexFormData['settings']['conceptUid']; + if ((int) $conceptUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_concept', $conceptUid); + $filterParts[] = $record['name']; + } + $hotelUid = $flexFormData['settings']['hotelUid']; + if ((int) $hotelUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $hotelUid); + $filterParts[] = $record['name']; + } + if ($flexFormData['settings']['dateFrom']) { + $filterParts[] = 'von ' . date('d.m.Y', $flexFormData['settings']['dateFrom']); + } + if ($flexFormData['settings']['dateTo']) { + $filterParts[] = 'bis ' . date('d.m.Y', $flexFormData['settings']['dateTo']); + } + if ((bool) $flexFormData['settings']['busAvailable'] === true) { + $filterParts[] = 'Busreisen'; + } + $itemContent .= implode(', ', $filterParts); + break; + case 'single': + $itemContent = 'Teaser Produkt
'; + $productUid = $flexFormData['settings']['productUid']; + $record = BackendUtility::getRecord('tx_epproducts_domain_model_product', $productUid); + $itemContent .= $record['name']; + $hotelUid = $flexFormData['settings']['hotelUid']; + if ((int) $hotelUid > 0) { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $hotelUid); + $itemContent .= ', ' . $record['name']; + } + break; + } + } + + } + + /** + * @param array $row + * @return array + */ + protected function getFlexFormData(array $row) + { + /** @var FlexFormService $flexFormService */ + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + return $flexFormService->convertFlexFormContentToArray($row['pi_flexform']); + } + + /** + * @param array $flexFormData + * @return string + */ + protected function getMode(array $flexFormData) + { + $controllerAction = $flexFormData['switchableControllerActions']; + return substr($controllerAction, strpos($controllerAction, '>') + 1); + } + + /** + * @param string $type + * @param integer $uid + * + * @return string + */ + protected function getName($type, $uid) + { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_' . $type, $uid); + return $record['name']; + } + +} diff --git a/packages/ep_products/Classes/Hooks/Tcemain.php b/packages/ep_products/Classes/Hooks/Tcemain.php new file mode 100644 index 0000000..55ef7e7 --- /dev/null +++ b/packages/ep_products/Classes/Hooks/Tcemain.php @@ -0,0 +1,119 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Traits\DbConnectionTrait; +use TYPO3\CMS\Core\DataHandling\DataHandler; +use TYPO3\CMS\Core\Messaging\FlashMessage; +use TYPO3\CMS\Core\Messaging\FlashMessageService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; + +class Tcemain +{ + use DbConnectionTrait; + + /** + * @var array + */ + protected $tables = [ + 'tx_epproducts_domain_model_country', + 'tx_epproducts_domain_model_region', + 'tx_epproducts_domain_model_city' + ]; + + /** + * @param array $fields + * @param string $table + * @param int $id + * @param DataHandler $obj + * @throws \Doctrine\DBAL\DBALException + */ + public function processDatamap_preProcessFieldArray(array &$fields, $table, $id, DataHandler $obj) + { + // Render flash message in case of duplicate entry + if ($table === 'tx_epproducts_domain_model_matchcode' && $fields['code'] !== '') { + $code = $fields['code']; + $qb = $this->getDbConnection()->createQueryBuilder(); + $query = $qb + ->select('uid') + ->from('tx_epproducts_domain_model_matchcode') + ->where('code = :code') + ->andWhere('deleted = 0') + ->setParameter('code', $code) + ; + if (is_numeric($id)) { + $query + ->andWhere('uid != :uid') + ->setParameter('uid', $id) + ; + } + $existing = $query->execute()->fetch(); + if ($existing) { + $this->addFlashMessage('Der Hotelcode ' . $code . ' ist schon vergeben worden.', 'Hinweis', FlashMessage::WARNING); + } + } + if ($table === 'tx_epproducts_domain_model_product' && $fields['code'] !== '') { + $code = $fields['code']; + $qb = $this->getDbConnection()->createQueryBuilder(); + $query = $qb + ->select('uid') + ->from('tx_epproducts_domain_model_product') + ->where('code = :code') + ->andWhere('deleted = 0') + ->setParameter('code', $code); + if (is_numeric($id)) { + $query + ->andWhere('uid != :uid') + ->setParameter('uid', $id) + ; + } + $existing = $query->execute()->fetch(); + if ($existing) { + $this->addFlashMessage('Der Productcode ' . $code . ' ist schon vergeben worden.', 'Hinweis', FlashMessage::WARNING); + } + } + } + + /** + * @param string $message + * @param string $title + * @param int $severity + */ + protected function addFlashMessage($message, $title, $severity) + { + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */ + $flashMessageService = $objectManager->get(FlashMessageService::class); + $messageQueue = $flashMessageService->getMessageQueueByIdentifier(); + /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $message */ + $message = GeneralUtility::makeInstance(FlashMessage::class, $message, $title, $severity, true); + $messageQueue->addMessage($message); + } + +} diff --git a/packages/ep_products/Classes/Http/ApiKeyAuthenticator.php b/packages/ep_products/Classes/Http/ApiKeyAuthenticator.php new file mode 100644 index 0000000..3d87c03 --- /dev/null +++ b/packages/ep_products/Classes/Http/ApiKeyAuthenticator.php @@ -0,0 +1,45 @@ +extensionConfiguration = $extensionConfiguration; + } + + public function isAuthorized(ServerRequestInterface $request): bool + { + $configured = (string) ($this->extensionConfiguration->get('ep_products')['cmsApiKey'] ?? ''); + + if ('' === $configured) { + return false; + } + + // Header first: query-string keys end up in access logs. `key` stays accepted for parity + // with the existing /api/hotel and /api/product calls. + $provided = $request->getHeaderLine('X-Api-Key'); + + if ('' === $provided) { + $provided = (string) ($request->getQueryParams()['key'] ?? ''); + } + + return hash_equals($configured, $provided); + } +} diff --git a/packages/ep_products/Classes/Log/Writer.php b/packages/ep_products/Classes/Log/Writer.php new file mode 100644 index 0000000..f57728b --- /dev/null +++ b/packages/ep_products/Classes/Log/Writer.php @@ -0,0 +1,40 @@ +getData(); + if (!empty($recordData)) { + if (isset($recordData['exception']) && $recordData['exception'] instanceof \Exception) { + $recordData['exception'] = (string)$recordData['exception']; + } + $data = '- ' . json_encode($recordData); + } + + $fieldValues = [ + 'request_id' => $record->getRequestId(), + 'time_micro' => $record->getCreated(), + 'tstamp' => (int) $record->getCreated(), + 'component' => $record->getComponent(), + 'level' => LogLevel::normalizeLevel($record->getLevel()), + 'message' => $record->getMessage(), + 'data' => $data + ]; + + GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionForTable($this->logTable) + ->insert($this->logTable, $fieldValues); + + return $this; + } +} diff --git a/packages/ep_products/Classes/Middleware/ApiMiddleware.php b/packages/ep_products/Classes/Middleware/ApiMiddleware.php new file mode 100644 index 0000000..3edf691 --- /dev/null +++ b/packages/ep_products/Classes/Middleware/ApiMiddleware.php @@ -0,0 +1,328 @@ +hotelImageService = $hotelImageService; + $this->productImageService = $productImageService; + $this->regionImageService = $regionImageService; + $this->fileRepository = $fileRepository; + $this->imageService = $imageService; + $this->contentFeedApi = $contentFeedApi; + $this->authenticator = $authenticator; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $path = $request->getUri()->getPath(); + + if ('/api/hotel' === $path) { + return $this->handleHotelRequest($request); + } + + if ('/api/product' === $path) { + return $this->handleProductRequest($request); + } + + if (0 === strpos($path, ContentFeedApi::PATH_PREFIX)) { + return $this->contentFeedApi->handle($request); + } + + return $handler->handle($request); + } + + private function handleHotelRequest(ServerRequestInterface $request): ResponseInterface + { + if (!$this->authenticator->isAuthorized($request)) { + return new JsonResponse(['message' => 'Invalid or missing api key'], 403); + } + + $hotelCode = $request->getQueryParams()['hotel'] ?? null; + + if (null === $hotelCode) { + return new JsonResponse(['message' => 'No hotel code provided'], 400); + } + + $data = $this->fetchHotelData($hotelCode); + + if ([] === $data) { + return new JsonResponse(['message' => 'Hotel not found'], 404); + } + + return new JsonResponse($data); + } + + private function handleProductRequest(ServerRequestInterface $request): ResponseInterface + { + if (!$this->authenticator->isAuthorized($request)) { + return new JsonResponse(['message' => 'Invalid or missing api key'], 403); + } + + $productCode = $request->getQueryParams()['product'] ?? null; + $hotelCode = $request->getQueryParams()['hotel'] ?? null; + + if (null === $productCode) { + return new JsonResponse(['message' => 'No product code provided'], 400); + } + + $data = $this->fetchProductData($productCode); + + if (null === $data) { + return new JsonResponse(['message' => 'Product not found'], 404); + } + + if (null !== $hotelCode) { + $data['hotel'] = $this->fetchHotelData($hotelCode); + } + + return new JsonResponse($data); + } + + private function fetchProductData(string $productCode): ?array + { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_epproducts_domain_model_product'); + + $data = $queryBuilder + ->select( + 'product.uid as productUid', + 'product.name as productName', + 'product.code as productCode', + 'product.region as regionUid', + 'country.name as countryName', + 'city.name as cityName', + ) + ->from('tx_epproducts_domain_model_product', 'product') + ->leftJoin('product', 'tx_epproducts_domain_model_country', 'country', 'product.country = country.uid') + ->leftJoin('product', 'tx_epproducts_domain_model_city', 'city', 'product.city = city.uid') + ->leftJoin('product', 'tx_epproducts_domain_model_matchcode', 'matchcode', 'product.uid = matchcode.entity AND matchcode.entity_type="hotel"') + ->where($queryBuilder->expr()->eq('product.code', $queryBuilder->createNamedParameter($productCode))) + ->orWhere($queryBuilder->expr()->eq('matchcode.code', $queryBuilder->createNamedParameter($productCode))) + ->setMaxResults(1) + ->execute() + ->fetchAssociative() + ; + + if (false === $data) { + return null; + } + + $images = $this->serializeImages($this->productImageService->getImages((int) $data['productUid'])); + + return [ + 'product' => [ + 'name' => $data['productName'], + 'code' => $data['productCode'], + 'images' => $images, + ], + 'country' => [ + 'name' => $data['countryName'], + ], + 'region' => $this->fetchRegionData((int) $data['regionUid']), + 'city' => [ + 'name' => $data['cityName'], + ], + ]; + } + + private function fetchHotelData(string $hotelCode): array + { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_epproducts_domain_model_hotel'); + + $hotel = $queryBuilder + ->select( + 'hotel.uid', + 'hotel.name', + 'hotel.code', + 'hotel.description', + 'hotel.features', + 'hotel.room_types', + 'hotel.additional_information', + 'hotel.address', + 'hotel.beds_count', + 'hotel.parking_count', + 'hotel.altitude', + 'hotel.shower', + 'hotel.partyroom', + 'hotel.gamesroom', + 'hotel.sauna', + 'hotel.wifi', + 'hotel.distance_bus', + 'hotel.distance_lift', + 'hotel.region', + ) + ->from('tx_epproducts_domain_model_hotel', 'hotel') + ->leftJoin('hotel', 'tx_epproducts_domain_model_matchcode', 'alias', 'hotel.uid = alias.entity AND alias.entity_type="hotel"') + ->where( + $queryBuilder->expr()->orX( + $queryBuilder->expr()->eq('hotel.code', $queryBuilder->createNamedParameter($hotelCode)), + $queryBuilder->expr()->eq('alias.code', $queryBuilder->createNamedParameter($hotelCode)), + ) + ) + ->setMaxResults(1) + ->execute() + ->fetchAssociative() + ; + + if (false === $hotel) { + return []; + } + + $images = $this->serializeImages($this->hotelImageService->getImages((int) $hotel['uid'])); + + return [ + 'name' => $hotel['name'], + 'code' => $hotel['code'], + 'description' => $hotel['description'], + 'features' => $hotel['features'], + 'room_types' => $hotel['room_types'], + 'additional_information' => $hotel['additional_information'], + 'address' => $hotel['address'], + 'images' => $images, + 'icons' => HotelIconUtility::getIcons($hotel), + 'region' => $this->fetchRegionData((int) $hotel['region']), + ]; + } + + private function fetchRegionData(int $regionUid): array + { + if (0 === $regionUid) { + return []; + } + + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_epproducts_domain_model_region'); + + $region = $queryBuilder + ->select( + 'region.uid', + 'region.name', + 'region.latitude', + 'region.longitude', + 'region.ski_area', + 'region.ski_area_extended', + 'region.description', + 'region.news', + 'region.length', + 'region.height', + 'region.lifts', + 'webcam.url as webcamUrl', + ) + ->from('tx_epproducts_domain_model_region', 'region') + ->leftJoin('region', 'tx_epproducts_domain_model_webcam', 'webcam', 'region.webcam = webcam.uid') + ->where($queryBuilder->expr()->eq('region.uid', $queryBuilder->createNamedParameter($regionUid, \PDO::PARAM_INT))) + ->setMaxResults(1) + ->execute() + ->fetchAssociative() + ; + + if (false === $region) { + return []; + } + + $images = $this->serializeImages($this->regionImageService->getImages((int) $region['uid'])); + $regionMaps = $this->serializeFileReferences( + $this->fileRepository->findByRelation('tx_epproducts_domain_model_region', 'region_maps', (int) $region['uid']) + ); + + return [ + 'name' => $region['name'], + 'latitude' => $region['latitude'], + 'longitude' => $region['longitude'], + 'webcam' => $region['webcamUrl'], + 'ski_area' => $region['ski_area'], + 'ski_area_extended' => $region['ski_area_extended'], + 'description' => $region['description'], + 'news' => $region['news'], + 'length' => $region['length'], + 'altitude' => $region['height'], + 'lifts' => $region['lifts'], + 'images' => $images, + 'region_maps' => $regionMaps, + ]; + } + + /** + * The 'original' entry of AbstractImageService::getImages() holds raw FileReference objects, + * which are needed by Fluid templates elsewhere but serialize to empty objects in JSON responses. + * We replace it with the same {url, alt, title} shape used for 'region_maps'. + */ + private function serializeImages(array $images): array + { + return [ + 'original' => $this->serializeFileReferences($images['original']), + 'resized' => $images['resized'], + ]; + } + + private function serializeFileReferences(array $files): array + { + return array_map(function ($file) { + return [ + 'url' => $this->imageService->getImageUri($file, true), + 'alt' => $file->getProperty('alternative'), + 'title' => $file->getProperty('title'), + ]; + }, $files); + } +} diff --git a/packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php b/packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php new file mode 100644 index 0000000..4f3409a --- /dev/null +++ b/packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php @@ -0,0 +1,65 @@ +authenticator = $authenticator; + $this->configuration = $configuration; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $routing = $request->getAttribute('routing'); + + if (!$routing instanceof PageArguments + || (string) ManifestBuilder::CONTENT_PAGE_TYPE !== $routing->getPageType() + ) { + return $handler->handle($request); + } + + if (!$this->authenticator->isAuthorized($request)) { + // 404, not 403: an unauthenticated caller should not learn the type exists. + return GeneralUtility::makeInstance(ErrorController::class) + ->pageNotFoundAction($request, 'The requested page does not exist'); + } + + if (!$this->configuration->isEnabled()) { + return ContentFeedApi::disabledResponse(); + } + + return $handler->handle($request); + } +} diff --git a/packages/ep_products/Classes/Middleware/StagingEnvironmentMiddleware.php b/packages/ep_products/Classes/Middleware/StagingEnvironmentMiddleware.php new file mode 100644 index 0000000..f71aec7 --- /dev/null +++ b/packages/ep_products/Classes/Middleware/StagingEnvironmentMiddleware.php @@ -0,0 +1,43 @@ +getQueryParams(); + + if (!isset($queryParams['ep_staging'])) { + return $handler->handle($request); + } + + $isStaging = '1' === (string) $queryParams['ep_staging']; + + // Update $_COOKIE so the value is available during the current request + if ($isStaging) { + $_COOKIE[self::COOKIE_NAME] = '1'; + } else { + unset($_COOKIE[self::COOKIE_NAME]); + } + + $response = $handler->handle($request); + + // Set the cookie on the response for subsequent browser requests + if ($isStaging) { + $cookie = new Cookie(self::COOKIE_NAME, '1', 0, '/', null, false, true, false, Cookie::SAMESITE_LAX); + } else { + $cookie = new Cookie(self::COOKIE_NAME, '', 1, '/', null, false, true, false, Cookie::SAMESITE_LAX); + } + + return $response->withAddedHeader('Set-Cookie', (string) $cookie); + } +} diff --git a/packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php b/packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php new file mode 100644 index 0000000..82fbeb8 --- /dev/null +++ b/packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php @@ -0,0 +1,71 @@ +getAttribute('normalizedParams'); + $uri = $params->getRequestUri(); + + if (strpos($uri, self::PREFIX_PATH_SEGMENT) === false) { + return $handler->handle($request); + } + + // Ensure trailing slash for comparison + $uri = rtrim($uri, '/') . '/'; + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $query = $qb + ->select('*') + ->from('tx_epproducts_domain_model_staticsearchparams') + ->where('CONCAT("/", :prefix, "/", path_segment, "/")=LOWER(:uri)') + ->andWhere('deleted = 0') + ->andWhere('hidden = 0') + ->setParameters([ + 'prefix' => self::PREFIX_PATH_SEGMENT, + 'uri' => $uri, + ]) + ; + + $row = $query->execute()->fetchAssociative(); + + if (!$row) { + return $handler->handle($request); + } + + $filterService = GeneralUtility::makeInstance(FilterService::class); + + $filterSettings = $filterService->getFilterSettingsFromStaticSearchParams($row); + $filterSettingsEncoder = new FilterSettingsEncoder(); + $encodedFilterSettings = $filterSettingsEncoder->encode($filterSettings); + + $searchPageUid = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products', 'searchPageUid'); + + $site = $request->getAttribute('site'); + $redirectUri = $site->getRouter()->generateUri($searchPageUid, ['f' => $encodedFilterSettings]); + + return new RedirectResponse($redirectUri, self::REDIRECT_RESPONSE_CODE); + } +} diff --git a/packages/ep_products/Classes/MyEP/ApiClient.php b/packages/ep_products/Classes/MyEP/ApiClient.php new file mode 100644 index 0000000..ca90c3b --- /dev/null +++ b/packages/ep_products/Classes/MyEP/ApiClient.php @@ -0,0 +1,506 @@ +requestJson('GET', 'last-update'); + + if (!isset($data['timestamp']) || null === $data['timestamp'] || '' === $data['timestamp']) { + return null; + } + + try { + // The API reports Europe/Berlin local time without an offset. + return new \DateTimeImmutable($data['timestamp'], new \DateTimeZone('Europe/Berlin')); + } catch (\Throwable $e) { + $message = sprintf('Invalid timestamp returned by MyEP API for GET last-update: %s', $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } + } + + /** + * One page of the product export: one row per travel date × hotel in `data`, + * and `meta` describing the whole export. + * + * @throws ApiException + */ + public function getProductExport(int $page, int $perPage): array + { + return $this->requestJson('GET', 'exports/products', [ + 'query' => [ + 'page' => $page, + 'perPage' => $perPage, + ], + ]); + } + + /** + * @throws ApiException + */ + public function getPickups(): array + { + return $this->requestJson('GET', 'pickups'); + } + + /** + * @throws ApiException + */ + public function getTravels(): array + { + return $this->requestJson('GET', 'travels'); + } + + /** + * @throws ApiException + */ + public function registerAddress(array $addressData): void + { + $response = $this->request('POST', 'contactform', [ + 'json' => $addressData, + ]); + + // A conflict means the address is already registered, which is the normal answer to a + // resubmitted contact form and not worth reporting. + $this->assertStatusCode($response, [Response::HTTP_OK, Response::HTTP_CREATED, Response::HTTP_ACCEPTED, Response::HTTP_NO_CONTENT, Response::HTTP_CONFLICT], 'POST', 'contactform'); + $this->drainResponse($response); + } + + /** + * @throws ApiException + */ + public function getNewsletterLists(): array + { + return $this->requestJson('GET', 'newsletters'); + } + + /** + * @throws ApiException + */ + public function subscribeToNewsletters(NewsletterSubscriptionRequest $subscriptionRequest): array + { + $response = $this->request('POST', 'newsletter-subscriptions', [ + 'json' => $subscriptionRequest, + ]); + + $this->assertStatusCode($response, [Response::HTTP_OK, Response::HTTP_ACCEPTED], 'POST', 'newsletter-subscriptions'); + + return $this->decodeResponse($response, 'POST', 'newsletter-subscriptions'); + } + + /** + * @throws ApiException + */ + public function getTravel(string $productCode): array + { + return $this->requestJson('GET', 'travels/' . $productCode); + } + + /** + * @throws ApiException + */ + public function getPickupsPlanning(string $travelCode): array + { + return $this->requestJson('GET', 'pickups-planning/' . $travelCode); + } + + /** + * @throws ApiException + */ + public function getPriceConfigData(string $hotelCode): array + { + return $this->requestJson('GET', 'contingents/calendar', [ + 'query' => [ + 'hotelCode' => $hotelCode, + ], + ]); + } + + /** + * @throws ApiException + */ + public function getPriceTableData(string $hotelCode, string $year): array + { + return $this->requestJson('GET', 'contingents/prices', [ + 'query' => [ + 'hotelCode' => $hotelCode, + 'year' => $year, + ], + ]); + } + + /** + * @throws ApiException + */ + private function requestJson(string $method, string $uri, array $options = [], array $expectedStatusCodes = [Response::HTTP_OK]): array + { + $response = $this->request($method, $uri, $options); + + $this->assertStatusCode($response, $expectedStatusCodes, $method, $uri); + + return $this->decodeResponse($response, $method, $uri); + } + + /** + * @throws ApiException + */ + private function request(string $method, string $uri, array $options = []): ResponseInterface + { + $response = $this->sendRequest($method, $uri, $options); + + if (false === in_array($this->readStatusCode($response, $method, $uri), self::UNAUTHORIZED_STATUS_CODES, true)) { + return $response; + } + + // The token outlives a single PHP request, so one the API has stopped accepting - after + // a MyEP restart or a revoked client - would keep failing every call for the rest of its + // cached lifetime unless it is actively discarded here. + $this->logWarning(sprintf('MyEP API rejected the cached access token for %s %s, re-authenticating once', $method, $uri)); + $this->discardAccessToken(); + + return $this->sendRequest($method, $uri, $options); + } + + /** + * @throws ApiException + */ + private function sendRequest(string $method, string $uri, array $options): ResponseInterface + { + // Resolved here rather than baked into the client's default options so that the shared + // client survives a token change, and so the retry above picks up the fresh token. + $options['auth_bearer'] = $this->getAccessToken($this->getConfiguration())->getToken(); + + try { + return $this->getHttpClient()->request($method, $uri, $options); + } catch (TransportExceptionInterface $e) { + $message = sprintf('MyEP API transport error for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } + } + + /** + * @param int[] $expectedStatusCodes + * + * @throws ApiException + */ + private function assertStatusCode(ResponseInterface $response, array $expectedStatusCodes, string $method, string $uri): void + { + $statusCode = $this->readStatusCode($response, $method, $uri); + + if (true === in_array($statusCode, $expectedStatusCodes, true)) { + return; + } + + $message = sprintf('MyEP API returned unexpected status %d for %s %s', $statusCode, $method, $uri); + $this->logWarning($message); + + throw new ApiException($message, $statusCode); + } + + /** + * Reading the status completes the transfer, so this is also what turns the lazy response + * returned by the client into a finished request. Symfony keeps the status on the response, + * so calling this twice costs nothing. + * + * @throws ApiException + */ + private function readStatusCode(ResponseInterface $response, string $method, string $uri): int + { + try { + return $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + $message = sprintf('MyEP API transport error while reading status for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } + } + + /** + * Reads and discards a body nobody else is going to look at. Destroying an unconsumed + * response cancels the transfer, which closes the connection instead of returning it to + * the pool, so the next call would have to handshake again. + */ + private function drainResponse(ResponseInterface $response): void + { + try { + $response->getContent(false); + } catch (TransportExceptionInterface $e) { + // The status has already been asserted, so a failure here costs nothing but the + // connection reuse this method exists for. + } + } + + /** + * @throws ApiException + */ + private function decodeResponse(ResponseInterface $response, string $method, string $uri): array + { + try { + return $response->toArray(false); + } catch (DecodingExceptionInterface $e) { + $message = sprintf('MyEP API returned invalid JSON for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } catch (TransportExceptionInterface $e) { + $message = sprintf('MyEP API transport error while reading body for %s %s: %s', $method, $uri, $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } + } + + private function getHttpClient(): HttpClientInterface + { + if (null !== static::$httpClient) { + return static::$httpClient; + } + + $config = $this->getConfiguration(); + + $options = []; + + // Without these the client falls back to PHP's default_socket_timeout (commonly 60s), + // so a stalled API pins a PHP worker for a minute per request. The defaults live in + // code rather than in ext_conf_template.txt: ExtensionConfiguration::get() returns the + // stored configuration verbatim and only syncs the template when an extension has no + // configuration at all, so a newly added template key is absent until someone saves + // extension configuration by hand. Falling back to 0 there would silently mean "no + // timeout" - the opposite of what this guard is for. An explicit 0 still disables. + $timeout = (int)($config['myEpApiTimeout'] ?? self::DEFAULT_TIMEOUT); + if ($timeout > 0) { + $options['timeout'] = $timeout; + } + $maxDuration = (int)($config['myEpApiMaxDuration'] ?? self::DEFAULT_MAX_DURATION); + if ($maxDuration > 0) { + $options['max_duration'] = $maxDuration; + } + + return static::$httpClient = HttpClient::createForBaseUri($config['myEpApiBaseUrl'], $options); + } + + /** + * Resolved lazily rather than injected: this class is also instantiated through Extbase's + * legacy object container (EP\EpTheme\Form\BpnApiFinisher asks for it via injectApiClient), + * and that container resolves constructor arguments by reflection, mapping an interface to + * a class name by stripping the "Interface" suffix (Container::getImplementationClassName()). + * A constructor typed against FrontendInterface therefore fatals there with + * "Class TYPO3\CMS\Core\Cache\Frontend\Frontend does not exist". + */ + private function getCache(): FrontendInterface + { + if (null === $this->cache) { + $this->cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('ep_products_cache'); + } + + return $this->cache; + } + + private function getConfiguration(): array + { + if (null === $this->configuration) { + $this->configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); + } + + return $this->configuration; + } + + /** + * @throws ApiException + */ + private function getAccessToken(array $config): AccessToken + { + if (true === $this->isTokenUsable(static::$accessToken)) { + return static::$accessToken; + } + + // The static above only lives for one PHP request, so without this second level every + // single request would re-authenticate before it could fetch any data. + $cachedToken = $this->getCache()->get(self::ACCESS_TOKEN_CACHE_IDENTIFIER); + if (is_array($cachedToken) && isset($cachedToken['access_token'])) { + $token = new AccessToken($cachedToken); + if (true === $this->isTokenUsable($token)) { + static::$accessToken = $token; + + return static::$accessToken; + } + } + + try { + static::$accessToken = $this->normalizeToken($this->getProvider($config)->getAccessToken('client_credentials')); + $this->cacheAccessToken(static::$accessToken); + } catch (IdentityProviderException $e) { + $message = sprintf('MyEP OAuth token request failed: %s', $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } catch (\UnexpectedValueException $e) { + $message = sprintf('MyEP OAuth token response was invalid: %s', $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } catch (GuzzleException $e) { + $message = sprintf('MyEP OAuth token transport failed: %s', $e->getMessage()); + $this->logError($message, ['exception' => $e]); + throw new ApiException($message, 0, $e); + } + + return static::$accessToken; + } + + /** + * AccessToken::hasExpired() throws for a token that reports no expiry, so it cannot be used + * as the check - a token without one is simply treated as unusable and replaced. The same + * margin as the cache lifetime is applied on read as well, so a token cannot lapse between + * this check and the API call that uses it, whichever node wrote the cache entry. + */ + private function isTokenUsable(?AccessToken $token): bool + { + if (null === $token) { + return false; + } + + $expires = $token->getExpires(); + + if (null === $expires) { + return false; + } + + return $expires - self::ACCESS_TOKEN_EXPIRY_MARGIN > time(); + } + + /** + * A token that reports no expiry cannot be validated on read - isTokenUsable() rejects it + * and AccessToken::hasExpired() throws for it - so it is given the fallback lifetime here, + * once, and the rest of the class can treat every token the same way. + */ + private function normalizeToken(AccessToken $token): AccessToken + { + if (null !== $token->getExpires()) { + return $token; + } + + return new AccessToken($token->jsonSerialize() + ['expires_in' => self::ACCESS_TOKEN_DEFAULT_TTL]); + } + + private function discardAccessToken(): void + { + static::$accessToken = null; + $this->getCache()->remove(self::ACCESS_TOKEN_CACHE_IDENTIFIER); + } + + private function cacheAccessToken(AccessToken $token): void + { + // normalizeToken() has already given a token without a reported expiry the fallback + // lifetime, so there is always a real expiry to subtract the margin from here. + $lifetime = $token->getExpires() - time() - self::ACCESS_TOKEN_EXPIRY_MARGIN; + + if ($lifetime <= 0) { + return; + } + + $this->getCache()->set(self::ACCESS_TOKEN_CACHE_IDENTIFIER, $token->jsonSerialize(), [], $lifetime); + } + + private function getProvider(array $config): AbstractProvider + { + return new GenericProvider([ + // The OAuth round trip goes through the provider's own Guzzle client, not the + // Symfony client configured in getHttpClient(), so it needs its own timeout - + // otherwise the token request hangs indefinitely and the data-request timeout + // never gets a chance to apply. AbstractProvider::getAllowedClientOptions() + // forwards 'timeout' (and 'proxy') into Guzzle; 0 means unlimited there too. + 'timeout' => (int)($config['myEpApiTimeout'] ?? self::DEFAULT_TIMEOUT), + 'clientId' => $config['myEpApiClientId'], + 'clientSecret' => $config['myEpApiClientSecret'], + 'redirectUri' => null, + 'urlAuthorize' => $config['myEpApiUrlAuthorize'], + 'urlAccessToken' => $config['myEpApiUrlToken'], + 'urlResourceOwnerDetails' => null, + 'scopes' => 'api', + ]); + } + + private function logError(string $message, array $context = []): void + { + if (null !== $this->logger) { + $this->logger->error($message, $context); + } + } + + private function logWarning(string $message, array $context = []): void + { + if (null !== $this->logger) { + $this->logger->warning($message, $context); + } + } +} diff --git a/packages/ep_products/Classes/MyEP/ApiException.php b/packages/ep_products/Classes/MyEP/ApiException.php new file mode 100644 index 0000000..b59a70c --- /dev/null +++ b/packages/ep_products/Classes/MyEP/ApiException.php @@ -0,0 +1,7 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Routing\Aspect\StaticMappableAspectInterface; + +class PassthroughMapper implements StaticMappableAspectInterface +{ + + /** + * @inheritDoc + */ + public function generate(string $value): ?string + { + return $value; + } + + /** + * @inheritDoc + */ + public function resolve(string $value): ?string + { + return $value; + } +} \ No newline at end of file diff --git a/packages/ep_products/Classes/Service/AbstractImageService.php b/packages/ep_products/Classes/Service/AbstractImageService.php new file mode 100644 index 0000000..070d038 --- /dev/null +++ b/packages/ep_products/Classes/Service/AbstractImageService.php @@ -0,0 +1,111 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Imaging\ImageManipulation\Area; +use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection; +use TYPO3\CMS\Core\Resource\FileReference; +use TYPO3\CMS\Core\Resource\FileRepository; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Extbase\Service\ImageService; + +abstract class AbstractImageService implements SingletonInterface +{ + /** + * @var FileRepository + */ + protected $fileRepository; + + /** + * @var ImageService + */ + protected $imageService; + + /** + * @var FrontendInterface + */ + protected $cache; + + public function __construct(FileRepository $fileRepository, ImageService $imageService, FrontendInterface $cache) + { + $this->fileRepository = $fileRepository; + $this->imageService = $imageService; + $this->cache = $cache; + } + + public function getImages($uid): array + { + $key = sprintf('images-%s-%d', $this->table, $uid); + if (false === ($value = $this->cache->get($key))) { + $original = $this->fileRepository->findByRelation( + $this->table, + $this->field, + $uid + ); + $resized = $this->generateResizedImages($original); + $value = [ + 'original' => $original, + 'resized' => $resized, + ]; + $this->cache->set($key, $value); + } + + return $value; + } + + public function generateResizedImages($images): array + { + $resized = []; + foreach ($this->imageSizes as $version => $instructions) + { + foreach ($images as $image) + { + $instructions['crop'] = $this->getCroppingInfo($image, $instructions); + $processedImage = $this->imageService->applyProcessingInstructions($image, $instructions); + $resized[$version][] = [ + 'url' => $this->imageService->getImageUri($processedImage, true), + 'alt' => $image->getProperty('alternative'), + 'title' => $image->getProperty('title'), + ]; + } + } + + return $resized; + } + + protected function getCroppingInfo(FileReference $image, array $processingInstructions): ?Area + { + $cropString = $image->getProperty('crop'); + $cropVariantCollection = CropVariantCollection::create((string) $cropString); + $cropVariant = $processingInstructions['cropVariant'] ?: 'default'; + $cropArea = $cropVariantCollection->getCropArea($cropVariant); + + return $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image); + } +} diff --git a/packages/ep_products/Classes/Service/ApiImportSource.php b/packages/ep_products/Classes/Service/ApiImportSource.php new file mode 100644 index 0000000..4db2f73 --- /dev/null +++ b/packages/ep_products/Classes/Service/ApiImportSource.php @@ -0,0 +1,233 @@ +apiClient = $apiClient; + } + + /** + * The whole export is fetched before anything is returned, so a walk that + * has to restart never leaves a stitched dataset behind. + * + * @throws ApiException + */ + public function acquire(): ?ProductImport + { + if (!$this->isNewImportAvailable()) { + return null; + } + + for ($walk = 1; $walk <= self::MAX_WALKS; $walk++) { + $export = $this->walkPages(); + if ($export === false) { + continue; + } + if ($export === null) { + return null; + } + + [$rows, $sourceGeneratedAt] = $export; + $baseline = $this->getImportedTimestamp(); + if ($baseline !== null && $sourceGeneratedAt <= $baseline) { + return null; + } + + return new ProductImport($rows, $sourceGeneratedAt); + } + + throw new ApiException(sprintf( + 'MyE&P replaced its product export during each of %d page walks, giving up until the next run', + self::MAX_WALKS + )); + } + + public function markImported(ProductImport $import): void + { + $sourceGeneratedAt = $import->getSourceGeneratedAt(); + if ($sourceGeneratedAt !== null) { + $file = $this->baselinePath(); + GeneralUtility::mkdir_deep(dirname($file)); + GeneralUtility::writeFile($file, $sourceGeneratedAt->format(\DateTimeInterface::ATOM), false); + } + + (new ImportTimestampService())->writeImportTimestamp(self::IMPORT_MARKER); + } + + /** + * True when MyE&P reports an export newer than the last imported one, or + * nothing has been imported yet. A missing timestamp from MyE&P means + * "not importable now", never "import". + * + * @throws ApiException + */ + public function isNewImportAvailable(): bool + { + $lastUpdateAt = $this->getApiClient()->getLastUpdateAt(); + if ($lastUpdateAt === null) { + return false; + } + + $baseline = $this->getImportedTimestamp(); + + return $baseline === null || $lastUpdateAt > $baseline; + } + + /** + * `meta.sourceGeneratedAt` of the last successfully imported export. + */ + public function getImportedTimestamp(): ?\DateTimeImmutable + { + $file = $this->baselinePath(); + if (!is_file($file)) { + return null; + } + + return $this->parseTimestamp(trim((string) file_get_contents($file))); + } + + /** + * @return array{0: array[], 1: \DateTimeImmutable}|false|null the rows and + * the export's timestamp; null when the export is not importable now; + * false when the export changed mid-walk and the walk must restart + * @throws ApiException + */ + private function walkPages() + { + $rows = []; + $sourceGeneratedAt = null; + $page = 1; + + do { + $body = $this->getApiClient()->getProductExport($page, self::PER_PAGE); + $meta = $body['meta'] ?? null; + if (!is_array($meta) || !isset($body['data']) || !is_array($body['data'])) { + throw new ApiException(sprintf('MyE&P product export page %d is malformed', $page)); + } + + $pageGeneratedAt = $this->parseTimestamp($meta['sourceGeneratedAt'] ?? null); + if ($page === 1) { + if ($pageGeneratedAt === null) { + $this->logWarning('MyE&P product export has no source timestamp; skipping product import'); + return null; + } + $sourceGeneratedAt = $pageGeneratedAt; + } elseif ($pageGeneratedAt === null || $pageGeneratedAt != $sourceGeneratedAt) { + $this->logWarning('MyE&P product export changed during the page walk; restarting', [ + 'page' => $page, + 'expected' => $sourceGeneratedAt->format(\DateTimeInterface::ATOM), + 'actual' => $meta['sourceGeneratedAt'] ?? null, + ]); + return false; + } + + foreach ($body['data'] as $row) { + $rows[] = $row; + } + + $totalPages = (int) ($meta['totalPages'] ?? 0); + $page++; + } while ($page <= $totalPages); + + $total = (int) ($meta['total'] ?? -1); + if (count($rows) !== $total) { + throw new ApiException(sprintf( + 'MyE&P product export is incomplete: %d rows received, %d announced', + count($rows), + $total + )); + } + + if ((int) ($meta['skipped'] ?? 0) > 0) { + $this->logWarning('MyE&P skipped travel dates with unparseable dates', ['skipped' => $meta['skipped']]); + } + if ((int) ($meta['pickupsWithoutMasterData'] ?? 0) > 0) { + $this->logWarning( + 'MyE&P product export has pickups without master data', + ['pickupsWithoutMasterData' => $meta['pickupsWithoutMasterData']] + ); + } + + return [$rows, $sourceGeneratedAt]; + } + + /** + * @param mixed $value + */ + private function parseTimestamp($value): ?\DateTimeImmutable + { + if (!is_string($value) || $value === '') { + return null; + } + + try { + return new \DateTimeImmutable($value); + } catch (\Exception $e) { + return null; + } + } + + private function getApiClient(): ApiClient + { + if ($this->apiClient === null) { + $this->apiClient = GeneralUtility::makeInstance(ApiClient::class); + } + + return $this->apiClient; + } + + private function baselinePath(): string + { + return Environment::getVarPath() . '/transient/' . self::STAGING_SUBDIR . '/' . self::BASELINE_FILE; + } + + private function logWarning(string $message, array $context = []): void + { + if ($this->logger !== null) { + $this->logger->warning($message, $context); + } + } +} diff --git a/packages/ep_products/Classes/Service/ContingentDataService.php b/packages/ep_products/Classes/Service/ContingentDataService.php new file mode 100644 index 0000000..2c78761 --- /dev/null +++ b/packages/ep_products/Classes/Service/ContingentDataService.php @@ -0,0 +1,113 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\ContingentRepository; +use EP\EpProducts\Utility\BookingUrlUtility; +use League\Period\Period; + +class ContingentDataService +{ + /** + * @var ContingentRepository + */ + protected $contingentRepository; + + /** + * @param ContingentRepository $contingentRepository + */ + public function __construct(ContingentRepository $contingentRepository) + { + $this->contingentRepository = $contingentRepository; + } + + /** + * @param Hotel $hotel + * @param Product $product + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getAvailableContingents(Hotel $hotel, Product $product) + { + $data = $this->contingentRepository->getAvailableContingents($hotel, $product); + $contingents = []; + foreach ($data as $row) { + $contingents[$row['date']] = [ + 'date' => $row['date'], + 'minNights' => $row['minNights'], + 'total' => $row['total'], + 'capacity' => $row['capacity'], + ]; + } + + return $contingents; + } + + /** + * @param string $dateFrom + * @param string $dateTo + * @param Hotel $hotel + * @param Product $product + * @param string $myEpUrl + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getAvailableRooms( + string $dateFrom, + string $dateTo, + Hotel $hotel, + Product $product, + string $myEpUrl + ) { + $data = $this->contingentRepository->getAvailableRooms($dateFrom, $dateTo, $hotel, $product); + $period = new Period($dateFrom, $dateTo); + $nights = $period->dateInterval()->format('%a'); + + $rooms = []; + foreach ($data as $row) { + $row['bookingUrl'] = BookingUrlUtility::generateUrl([ + 'dateId' => $row['busProId'], + 'hotelId' => $row['hotelBusProId'], + 'myEpUrl' => $myEpUrl, + ]); + + $row['summer'] = $row['season'] === 's'; + + $row['servicesIncluded'] = unserialize($row['servicesIncluded'], ['allowed_classes' => false]); + $row['servicesOptional'] = unserialize($row['servicesOptional'], ['allowed_classes' => false]); + + $row['priceForSelection'] = $row['price'] + ($nights - $row['minNights']) * $row['additionalNightPrice']; + + $rooms[] = $row; + } + + return $rooms; + } +} diff --git a/packages/ep_products/Classes/Service/ContingentImportService.php b/packages/ep_products/Classes/Service/ContingentImportService.php new file mode 100644 index 0000000..bf99af8 --- /dev/null +++ b/packages/ep_products/Classes/Service/ContingentImportService.php @@ -0,0 +1,341 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use Doctrine\DBAL\Connection; +use EP\EpProducts\Domain\Model\Contingent; +use EP\EpProducts\Traits\DbConnectionTrait; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; + +class ContingentImportService implements SingletonInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + use DbConnectionTrait; + + public const XML_PATH = 'fileadmin/xmlexportzimmer'; + public const ROOMCODE_STATUS = 'BelKal'; + + /** + * @var string[] + */ + // protected $ignoredRoomTypes = ['PDGS', 'TEAM']; + protected $ignoredRoomTypes = ['PDGS']; + + /** + * @var ConfigurationManagerInterface + */ + protected $configurationManager; + + /** + * @var int + */ + protected $contingentsPid = 0; + + /** + * @var int + */ + protected $hotelsPid = 0; + + /** + * @var Connection + */ + protected $db; + + /** + * @throws \Doctrine\DBAL\DBALException + */ + public function __construct(ConfigurationManagerInterface $manager) + { + $this->configurationManager = $manager; + + $settings = GeneralUtility::removeDotsFromTS( + $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + + if (isset($settings['plugin']['tx_epproducts']['persistence']['storagePid'])) { + $this->contingentsPid = $settings['plugin']['tx_epproducts']['persistence']['storagePid']; + } elseif (isset($settings['module']['tx_epproducts']['persistence']['storagePid'])) { + $this->contingentsPid = $settings['module']['tx_epproducts']['persistence']['storagePid']; + } + + if (isset($settings['plugin']['tx_epproducts']['settings']['hotelsPid'])) { + $this->hotelsPid = $settings['plugin']['tx_epproducts']['settings']['hotelsPid']; + } elseif (isset($settings['module']['tx_epproducts']['settings']['hotelsPid'])) { + $this->hotelsPid = $settings['module']['tx_epproducts']['settings']['hotelsPid']; + } + + $this->db = $this->getDbConnection(); + } + + /** + * @return int + * + * @throws \Doctrine\DBAL\DBALException + */ + public function import() + { + $this->createTempTable(); + $this->logger->info('starting product import'); + $contingentCount = $this->parseXmlFilesInFolder(); + $this->clearTable(); + $this->copyTable(); + $this->clearTempTable(); + + return $contingentCount; + } + + public function isImportRequired(): bool + { + $timestampService = new ImportTimestampService(); + + return $timestampService->isImportRequired( + 'fileadmin/xmlexportzimmer/uebertragung.info', + 'fileadmin/contingents_import.info' + ); + } + + /** + * @return int + */ + public function parseXmlFilesInFolder() + { + $dateCount = 0; + $path = GeneralUtility::getFileAbsFileName(self::XML_PATH); + $folder = opendir($path); + while (($fileName = readdir($folder)) !== false) { + if (0 === strpos($fileName, 'HotelZimmer')) { + $dateCount += $this->parseContingentXmlFile($path, $fileName); + } + } + + return $dateCount; + } + + /** + * @param string $fileName + * + * @return int + */ + public function parseContingentXmlFile($path, $fileName) + { + $filePath = sprintf('%s/%s', $path, $fileName); + + if (null === $xmlData = $this->loadXmlFile($filePath)) { + return 0; + } + + return $this->parseContingent($xmlData); + } + + public function loadXmlFile($file): ?\SimpleXMLElement + { + libxml_use_internal_errors(true); + if (!file_exists($file)) { + return null; + } + /** @var \SimpleXMLElement $xmlData */ + if (!$xmlData = simplexml_load_string(file_get_contents($file))) { + libxml_clear_errors(); + + return null; + } + + return $xmlData; + } + + /** + * @throws \Doctrine\DBAL\DBALException + */ + public function createTempTable() + { + $this->db->executeStatement('CREATE TABLE IF NOT EXISTS tx_epproducts_domain_model_contingent_temp LIKE tx_epproducts_domain_model_contingent'); + } + + /** + * @throws \Doctrine\DBAL\DBALException + */ + public function clearTable() + { + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_contingent'); + } + + /** + * @throws \Doctrine\DBAL\DBALException + */ + public function copyTable() + { + $this->db->executeStatement('INSERT INTO tx_epproducts_domain_model_contingent SELECT * FROM tx_epproducts_domain_model_contingent_temp'); + } + + /** + * @throws \Doctrine\DBAL\DBALException + */ + public function clearTempTable() + { + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_contingent_temp'); + } + + /** + * @return int + */ + protected function parseContingent(\SimpleXMLElement $xmlContingent) + { + $hotelCode = (string) $xmlContingent['code']; + $sql = 'SELECT h.uid uid + FROM tx_epproducts_domain_model_hotel h + WHERE h.deleted = 0 AND h.hidden = 0 AND h.code = :code AND h.pid = :pid + LIMIT 0,1'; + $hotelData = $this->db->fetchAssociative($sql, ['code' => $hotelCode, 'pid' => $this->hotelsPid]); + + // Skip import in case hotel can't be found + if (false === $hotelData) { + $this->logger->warning('hotel not found', ['code' => $hotelCode]); + + return 0; + } + + $hotelData['busProId'] = (string) $xmlContingent['idbuspro']; + $hotelData['code'] = (string) $xmlContingent['code']; + + if ($xmlContingent->idbuspro_kontingent_aus) { + $linkedProductId = (int) $xmlContingent->idbuspro_kontingent_aus; + $path = GeneralUtility::getFileAbsFileName(self::XML_PATH); + if (null === $xmlContingent = $this->loadXmlFile(sprintf('%s/HotelZimmer_%d.xml', $path, $linkedProductId))) { + return 0; + } + } + + if (empty($xmlContingent->kapazitaeten)) { + $this->logger->warning('hotel has no contingents', ['code' => $hotelCode]); + + return 0; + } + + $roomTypes = $this->parseRoomTypes($xmlContingent->unterbringungen); + + return $this->parseRooms($xmlContingent->kapazitaeten, $hotelData, $roomTypes); + } + + /** + * @param \SimpleXMLElement $xmlTypes + * + * @return array + */ + protected function parseRoomTypes($xmlTypes) + { + $types = []; + foreach ($xmlTypes->unterbringung as $xmlType) { + $idBusPro = (string) $xmlType['idbuspro']; + $code = (string) $xmlType['code']; + $types[] = [ + 'idBusPro' => $idBusPro, + 'link' => isset($xmlType['idbuspro_kontingent_aus']) ? (string) $xmlType['idbuspro_kontingent_aus'] : null, + 'pax' => (int) $xmlType['pax_max'], + 'code' => $code, + 'label' => (string) $xmlType['zimmerbezeichnung'], + 'ctrl' => self::ROOMCODE_STATUS === $code, + ]; + } + + return $types; + } + + /** + * @param \SimpleXMLElement|\SimpleXMLElement[] $xmlDates + * + * @return int + */ + protected function parseRooms(\SimpleXMLElement $xmlDates, array $hotelData, array $roomTypes) + { + $dateCount = 0; + foreach ($xmlDates->kapazitaet as $xmlDate) { + $date = \DateTime::createFromFormat('d.m.Y', (string) $xmlDate['termin']); + foreach ($roomTypes as $roomType) { + $roomCode = $roomType['code']; + if (in_array($roomCode, $this->ignoredRoomTypes, true)) { + continue; + } + $roomTypeBusProId = $roomType['link'] ?: $roomType['idBusPro']; + $xpath = sprintf('zimmerliste/zimmer[@idbuspro="%d"]', $roomTypeBusProId); + $xmlRoom = $xmlDate->xpath($xpath)[0]; + if (null === $xmlRoom) { + continue; + } + $this->calculateContingent($xmlRoom, $roomType, $date, $hotelData); + } + ++$dateCount; + } + + return $dateCount; + } + + protected function calculateContingent(\SimpleXMLElement $xmlRoom, array $roomType, \DateTime $date, array $hotelData): void + { + $availablePax = 0; + $capacity = 0; + $status = Contingent::STATUS_OK; + $isControlRoom = true === $roomType['ctrl']; + if ($isControlRoom && 'A' === (string) $xmlRoom['status']) { + $status = Contingent::STATUS_ONREQUEST; + } elseif ($isControlRoom && 'S' === (string) $xmlRoom['status']) { + $status = Contingent::STATUS_BLOCKED; + } else { + $capacity = (int) $xmlRoom['kontingent'] * (int) $roomType['pax']; + $availableRooms = (int) $xmlRoom['frei'] > 0 ? (int) $xmlRoom['frei'] : 0; + $availablePax = $availableRooms * $roomType['pax']; + } + $minPrice = (int) $xmlRoom['abpreis']; + $minNights = (int) $xmlRoom['abpreis_naechte']; + $additionalNightMinPrice = (int) $xmlRoom['abpreis_verlaengerung']; + $additionalNightMinNights = (int) $xmlRoom['abpreis_verlaengerung_naechte']; + $roomLabel = $roomType['label']; + $this->db->insert( + 'tx_epproducts_domain_model_contingent_temp', + [ + 'pid' => $this->contingentsPid, + 'hotel' => $hotelData['uid'], + 'hotel_code' => $hotelData['code'], + 'hotel_bus_pro_id' => $hotelData['busProId'], + 'room_code' => $roomType['code'], + 'room_label' => $roomLabel, + 'pax' => $capacity, + 'available' => $availablePax, + 'status' => $status, + 'date' => $date->format('Y-m-d'), + 'min_price' => $minPrice, + 'min_nights' => $minNights, + 'additional_night_min_price' => $additionalNightMinPrice, + 'additional_night_min_nights' => $additionalNightMinNights, + ] + ); + } +} diff --git a/packages/ep_products/Classes/Service/DateImportService.php b/packages/ep_products/Classes/Service/DateImportService.php new file mode 100644 index 0000000..210a5ed --- /dev/null +++ b/packages/ep_products/Classes/Service/DateImportService.php @@ -0,0 +1,970 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use Doctrine\DBAL\Connection; +use EP\EpProducts\Domain\Model\Room; +use EP\EpProducts\Traits\DbConnectionTrait; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; +use TYPO3\CMS\Extbase\Service\CacheService; + +class DateImportService implements SingletonInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + use DbConnectionTrait; + + const CODE_PSEUDOPRICE = 'PDGS'; + const CODE_BABYROOM = 'Baby'; + const LABEL_BUS = 'Busfahrt'; + const LABEL_TRAIN = 'Zugfahrt'; + const STATUS_AVAILABLE = 'Frei'; + const STATUS_ON_REQUEST = 'Anfrage'; + const STATUS_STOP = 'Buchungsstop'; + + /** + * BusPro ids of the selection groups whose selections make up the included + * services. Matched by id rather than by label, so a group renamed upstream + * does not silently drop out of services_included. + */ + const SELECTION_GROUP_SKIPASS = 7; + const SELECTION_GROUP_OTHER = 8; + const SELECTION_GROUP_BOARD = 71; + const SELECTION_GROUP_ACCOMMODATION = 85; + const SELECTION_GROUP_PROGRAMME = 86; + const SELECTION_GROUP_COURSES = 87; + const SELECTION_GROUP_EVENTS = 89; + const SELECTION_GROUP_TRANSPORTATION = 90; + + /** + * Group 'Kunde' also carries trip types (Wochenreise, Midweekreise, ...), so + * group travel keys on the selection, not on the group's presence. Every + * date should carry either 'Gruppenkunde' or 'Endkunde'. + */ + const SELECTION_GROUP_CUSTOMER = 96; + const SELECTION_CUSTOMER_GROUP = 1418; + const SELECTION_CUSTOMER_END = 1419; + + /** + * Fallback for dates the office has not tagged with either selection: these + * trip type markers in front of the travel code's trailing date mean + * end-customer travel. + */ + const END_CUSTOMER_CODE_PATTERN = '/(?:MP|WE|KT|PM|MW|FA)\d{6}$/'; + + /** + * 'Leistungen Personal' occurs in no export so far, so its id is unknown and + * it is still matched by label. Register its id once the warning fires. + */ + const SELECTION_GROUP_LABEL_PERSONNEL = 'Leistungen Personal'; + + /** + * @var ConfigurationManagerInterface + */ + protected $configurationManager; + + /** + * @var int + */ + protected $pid = 0; + + /** + * @var array + */ + protected $hotelMappings = []; + + /** + * @var Connection + */ + protected $db; + + /** + * @var CacheService + */ + protected $cacheService; + + /** + * @var ProductImportSourceFactory + */ + protected $importSourceFactory; + + /** + * @var RoomTypeResolver + */ + protected $roomTypeResolver; + + /** + * @var array + */ + static protected $cache = [ + 'datesAdded' => [], + ]; + + /** + * @param ConfigurationManagerInterface $configurationManager + * @param CacheService $cacheService + * @param ProductImportSourceFactory $importSourceFactory + * @param RoomTypeResolver $roomTypeResolver + */ + public function __construct( + ConfigurationManagerInterface $configurationManager, + CacheService $cacheService, + ProductImportSourceFactory $importSourceFactory, + RoomTypeResolver $roomTypeResolver + ) { + $this->configurationManager = $configurationManager; + $this->cacheService = $cacheService; + $this->importSourceFactory = $importSourceFactory; + $this->roomTypeResolver = $roomTypeResolver; + + $settings = GeneralUtility::removeDotsFromTS( + $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + + if (isset($settings['plugin']['tx_epproducts']['persistence']['storagePid'])) { + $this->pid = $settings['plugin']['tx_epproducts']['persistence']['storagePid']; + } elseif (isset($settings['module']['tx_epproducts']['persistence']['storagePid'])) { + $this->pid = $settings['module']['tx_epproducts']['persistence']['storagePid']; + } + } + + /** + * @param iterable $rows one row per travel date × hotel, in the shape + * of GET /api/exports/products on MyE&P + * @return int + * @throws \Doctrine\DBAL\DBALException + */ + public function import(iterable $rows): int + { + $this->db = $this->getDbConnection(); + $this->getHotelMappings(); + $this->createTempTables(); + $this->logger->info('starting product import'); + $dateCount = $this->importProducts($rows); + $this->clearTables(); + $this->copyTables(); + $this->clearTempTables(); + $this->cacheService->clearPageCache(); + + return $dateCount; + } + + public function isImportRequired(): bool + { + return $this->importSourceFactory->get()->isNewImportAvailable(); + } + + /** + * Groups the rows by product and travel date, in the order they first + * appear, because a product code can map to more than one TYPO3 product and + * each of them imports all of the product's dates. + * + * @param iterable $rows + * @return int + */ + protected function importProducts(iterable $rows): int + { + $products = []; + foreach ($rows as $row) { + $code = $row['product']['code']; + if (!isset($products[$code])) { + $products[$code] = ['busProId' => (int) $row['product']['busProId'], 'dates' => []]; + } + $products[$code]['dates'][(int) $row['date']['busProId']][] = $row; + } + + $dateCount = 0; + foreach ($products as $code => $product) { + $dateCount += $this->importProduct((string) $code, $product['busProId'], $product['dates']); + } + + return $dateCount; + } + + /** + * Recreated on every run, so the temp tables always follow the schema of the + * live tables (a column type change would otherwise survive in them) and a + * crashed import cannot leave rows behind for the next one. + */ + public function createTempTables(): void + { + $this->db->executeStatement('DROP TABLE IF EXISTS tx_epproducts_domain_model_date_temp'); + $this->db->executeStatement('DROP TABLE IF EXISTS tx_epproducts_domain_model_room_temp'); + $this->db->executeStatement('CREATE TABLE tx_epproducts_domain_model_date_temp LIKE tx_epproducts_domain_model_date'); + $this->db->executeStatement('CREATE TABLE tx_epproducts_domain_model_room_temp LIKE tx_epproducts_domain_model_room'); + } + + public function clearTables(): void + { + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_date'); + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_room'); + } + + public function copyTables(): void + { + $this->db->executeStatement('INSERT INTO tx_epproducts_domain_model_date SELECT * FROM tx_epproducts_domain_model_date_temp'); + $this->db->executeStatement('INSERT INTO tx_epproducts_domain_model_room SELECT * FROM tx_epproducts_domain_model_room_temp'); + } + + public function clearTempTables(): void + { + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_date_temp'); + $this->db->executeStatement('TRUNCATE TABLE tx_epproducts_domain_model_room_temp'); + } + + /** + * @param string $code + * @param int $busProId + * @param array[][] $dates rows grouped by travel date + * @return int + */ + protected function importProduct(string $code, int $busProId, array $dates): int + { + $dateCount = 0; + + $sql = ' + SELECT p.uid uid, p.pid pid, p.daytrip daytrip, p.hide_booking_button, p.important important, + p.name product_name, p.searchable product_searchable, p.detail_page product_detail_page, + p.keywords product_keywords, p.teaser product_teaser, p.feature product_feature, + p.subline product_subline, p.country country, p.region region, p.city city, p.path_segment slug, + p.season season, p.concept concept, + cnt.name country_name, cnt.code country_code, cnt.keywords country_keywords, r.name region_name, + r.keywords region_keywords, r.feature region_feature, + c.name city_name, c.keywords city_keywords, + con.name concept_name, con.code concept_code, con.sorting concept_sorting, + con.popup_text concept_popup_text + FROM tx_epproducts_domain_model_product p + INNER JOIN tx_epproducts_domain_model_country cnt ON p.country = cnt.uid + INNER JOIN tx_epproducts_domain_model_region r ON p.region = r.uid + INNER JOIN tx_epproducts_domain_model_city c ON p.city = c.uid + INNER JOIN tx_epproducts_domain_model_concept con ON p.concept = con.uid + WHERE p.deleted = 0 AND p.hidden = 0 AND p.code = :code + '; + + $products = $this->db->fetchAllAssociative($sql, ['code' => $code]); + + // Skip import in case product can't be found + if (count($products) === 0) { + $this->logger->warning('product not found or daytrip', ['code' => $code]); + return 0; + } + foreach ($products as $product) { + $this->db->update( + 'tx_epproducts_domain_model_product', + ['bus_pro_id' => $busProId], + ['uid' => $product['uid']] + ); + $product['bus_pro_id'] = $busProId; + $product['product_fact'] = $this->getTopFactForRecord('tx_epproducts_product_fact_mm', $product['uid']); + $product['region_fact'] = $this->getTopFactForRecord('tx_epproducts_region_fact_mm', $product['region']); + foreach ($dates as $rows) { + $dateCount += $this->importDate($rows, $product); + } + } + + return $dateCount; + } + + /** + * @param array[] $rows one per hotel of the travel date + * @param array $product + * @return int + */ + protected function importDate(array $rows, array $product): int + { + $dateCount = 0; + + // Selection groups are date-level, so any hotel row tells. + $this->checkCustomerSelection(reset($rows)); + + foreach ($rows as $row) { + $date = $this->createDateRecord($product, $row); + + $hotelCode = $row['hotel']['code']; + $key = $hotelCode . $date['date_start']; + + // Import daytrip dates only once per hotel as room information is taken + // from contingents + if ($product['daytrip'] && \in_array($key, static::$cache['datesAdded'], true)) { + continue; + } + + if ($this->parseHotel($row, $date) !== false) { + $this->db->insert( + 'tx_epproducts_domain_model_date_temp', + $date + ); + $dateUid = $this->db->lastInsertId(); + $this->db->update( + 'tx_epproducts_domain_model_room_temp', + ['date' => $dateUid], + ['date' => '0'] + ); + $dateCount++; + } + + static::$cache['datesAdded'][] = $key; + } + $this->db->update( + 'tx_epproducts_domain_model_product', + ['dates' => $dateCount], + ['uid' => $product['uid']] + ); + return $dateCount; + } + + /** + * @param array $product + * @param array $row + * @return array + */ + protected function createDateRecord(array $product, array $row): array + { + $dateStart = \DateTime::createFromFormat('Y-m-d', $row['date']['from']); + $dateEnd = \DateTime::createFromFormat('Y-m-d', $row['date']['to']); + $nights = $dateStart->diff($dateEnd)->format('%d'); + + $busProId = (int) $row['date']['busProId']; + $code = (string) $row['date']['code']; + + $date = [ + 'pid' => $this->pid, + 'product' => $product['uid'], + 'product_pid' => $product['pid'], + 'product_name' => $product['product_name'], + 'product_fact' => $product['product_fact'], + 'product_searchable' => $product['product_searchable'], + 'product_detail_page' => $product['product_detail_page'], + 'product_keywords' => $product['product_keywords'], + 'product_feature' => $product['product_feature'], + 'product_teaser' => $product['product_teaser'], + 'product_subline' => $product['product_subline'], + 'product_slug' => $product['slug'], + 'country' => $product['country'], + 'country_name' => $product['country_name'], + 'country_code' => $product['country_code'], + 'country_keywords' => $product['country_keywords'], + 'region' => $product['region'], + 'region_name' => $product['region_name'], + 'region_fact' => $product['region_fact'], + 'region_keywords' => $product['region_keywords'], + 'region_feature' => $product['region_feature'], + 'city' => $product['city'], + 'city_name' => $product['city_name'], + 'city_keywords' => $product['city_keywords'], + 'concept' => $product['concept'], + 'concept_name' => $product['concept_name'], + 'concept_code' => $product['concept_code'], + 'concept_popup_text' => $product['concept_popup_text'], + 'concept_sorting' => $product['concept_sorting'], + 'season' => empty($product['season']) ? 'w' : $product['season'], + 'date_start' => $dateStart->format('Y-m-d'), + 'date_end' => $dateEnd->format('Y-m-d'), + 'nights' => $nights, + 'bus_pro_id' => $busProId, + 'code' => $code, + 'daytrip' => $product['daytrip'], + 'hide_booking_button' => $product['hide_booking_button'], + 'important' => $product['important'], + ]; + + $includedAccommodation = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_ACCOMMODATION); + $includedSkiPass = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_SKIPASS); + $includedBoard = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_BOARD); + $includedTransportation = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_TRANSPORTATION); + $includedProgramme = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_PROGRAMME); + $includedPersonnel = $this->parsePersonnelSelectionGroup($row['selectionGroups']); + $includedEvents = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_EVENTS); + $includedOther = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_OTHER); + $includedCourses = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_COURSES); + + $includedServices = [ + ...$includedAccommodation, + ...$includedSkiPass, + ...$includedTransportation, + ...$includedBoard, + ...$includedProgramme, + ...$includedCourses, + ...$includedEvents, + ...$includedPersonnel, + ...$includedOther, + ]; + + $date['services_included'] = serialize($includedServices); + $date['courses_included'] = serialize($includedCourses); + + if ($includedBoard) { + $date['board'] = implode(', ', $includedBoard); + $date['board_bus_pro_id'] = key($includedBoard); + } + + $skiPassIncluded = (int) (count($includedSkiPass) > 0); + $date['skipass_included'] = $skiPassIncluded; + + $date['group_travel'] = (int) $this->isGroupTravel($row); + + $servicesTransportation = $this->parseService($row['transportServices']); + $discount = $this->determineDiscount($servicesTransportation); + $date['discount'] = $discount; + + $this->processBusConditions($servicesTransportation, $date); + + $servicesTransportationOptional = []; + if (array_key_exists('BUS', $servicesTransportation)) { + $servicesTransportationOptional = array_filter($servicesTransportation['BUS'], function ($service) { + return $service['price'] !== 0; + }); + } + if (array_key_exists('BAH', $servicesTransportation)) { + $servicesTransportationOptional = array_merge( + $servicesTransportationOptional, + $servicesTransportation['BAH'] + ); + } + + if ($discount) { + $servicesTransportationOptional[] = [ + 'label' => 'Rabatt bei Eigenanreise', + 'price' => number_format($discount, 2, ',', '.'), + ]; + } + + $servicesGeneral = $this->parseService($row['dateServices']); + + $combinedServices = []; + + if (count($servicesTransportationOptional)) { + $this->addServices(['BUS' => $servicesTransportationOptional], $combinedServices); + } + + if (count($servicesGeneral)) { + $this->addServices($servicesGeneral, $combinedServices); + } + + foreach ($combinedServices as $key => $list) + { + uasort($list, function ($a, $b) { + return strcmp($a['label'], $b['label']); + }); + $combinedServices[$key] = $list; + } + $date['services_general'] = serialize($combinedServices); + + $pickups = []; + foreach ($row['pickups'] as $pickup) { + $busProId = (int) $pickup['busProId']; + if ($busProId === 0) { + continue; + } + $pickupDate = $dateStart->format('d.m.Y'); + $pickupTime = ''; + if ($pickup['datetime'] !== null) { + [$pickupDate, $pickupTime] = $this->parsePickupDateTime($pickup['datetime']); + } + $pickups[] = [ + 'bus_pro_id' => $busProId, + 'city' => $pickup['city'], + 'street' => $pickup['street'], + // A pickup without a stated price has always been shown as included. + 'price' => $pickup['price'] === null ? 0.0 : (float) $pickup['price'], + 'date' => $pickupDate, + 'time' => $pickupTime, + ]; + } + usort($pickups, function ($a, $b) { + if (empty($a['date']) || empty($b['date']) || empty($a['time']) || empty($b['time'])) { + return 0; + } + + $timeStampA = \DateTimeImmutable::createFromFormat('d.m.Y H:i', $a['date'].' '.$a['time'])->getTimestamp(); + $timeStampB = \DateTimeImmutable::createFromFormat('d.m.Y H:i', $b['date'].' '.$b['time'])->getTimestamp(); + + return $timeStampA <=> $timeStampB; + }); + $date['pickups'] = serialize($pickups); + + $date['guide'] = serialize($row['guide'] ?? []); + + return $date; + } + + /** + * @param array $row + * @param array $date + * @return bool + */ + protected function parseHotel(array $row, array &$date): bool + { + $code = $row['hotel']['code']; + $busProId = (int) $row['hotel']['busProId']; + + if (!array_key_exists($code, $this->hotelMappings)) { + $this->logger->warning('hotel not found', ['code' => $code]); + + return false; + } + + $hotel = $this->hotelMappings[$code]; + $hotel['fact'] = $this->getTopFactForRecord('tx_epproducts_hotel_fact_mm', $hotel['uid']); + + $services = []; + $generalServices = unserialize($date['services_general']); + $hotelServices = $this->parseService($row['hotelServices'], true); + $this->addServices($generalServices, $services); + $this->addServices($hotelServices, $services); + foreach ($services as $key => $list) { + uasort($list, function ($a, $b) { + return strcmp($a['label'], $b['label']); + }); + $services[$key] = $list; + } + + $this->parseRooms($row, $hotel['uid'], $date, $services); + + $date['hotel'] = $hotel['uid']; + $date['hotel_code'] = $hotel['code']; + $date['hotel_bus_pro_id'] = $busProId; + $date['hotel_fact'] = $hotel['fact']; + $date['hotel_name'] = $hotel['name']; + $date['hotel_type'] = $hotel['type']; + $date['hotel_keywords'] = $hotel['keywords']; + $date['hotel_header_title'] = $hotel['header_title']; + $date['hotel_category'] = $hotel['category']; + $date['hotel_teaser'] = $hotel['teaser']; + $date['hotel_detail_page'] = $hotel['detail_page']; + $date['earlybird'] = $hotel['earlybird']; + $date['new'] = $hotel['new']; + + return true; + } + + /** + * @param array $row + * @param int $hotelUid + * @param array $date + * @param array $services + */ + protected function parseRooms(array $row, $hotelUid, array &$date, array $services): void + { + $minPriceOverall = 0.0; + $minPriceAvailable = 0.0; + $minPax = 0; + $availableTotal = 0; + $boardCode = ''; + $count = 0; + + if ($row['pseudoPrice'] !== null) { + $date['pseudo_price'] = (float) $row['pseudoPrice']; + } + + foreach ($row['rooms'] as $roomRow) { + $code = $roomRow['code']; + $label = $roomRow['label']; + $roomMinPax = (int) $roomRow['minPax']; + $pax = (int) $roomRow['maxPax']; + + $available = (int) $roomRow['available']; + $price = (float) $roomRow['price']; + + // Skip rooms with zero price _and_ zero availability. + if (!$available && !$price) { + continue; + } + + $roomType = $this->roomTypeResolver->resolve($code, $label, $pax, $roomMinPax); + + if ($pax < 1 && $roomMinPax < 1) { + $this->logger->warning('room without occupancy', ['code' => $code, 'zimmertext' => $label]); + } + + if ($this->roomTypeResolver->needsReview($code, $label, $pax)) { + $this->logger->warning( + 'single-occupancy room without single-use marker', + ['code' => $code, 'zimmertext' => $label] + ); + } + + if (0 === $minPax || $roomMinPax < $minPax) { + $minPax = $roomMinPax; + } + + $availableTotal += $available * $pax; + + // Determine lowest price of available rooms + if ($code !== static::CODE_BABYROOM && $available > 0 && ($minPriceAvailable === 0.0 || $price < $minPriceAvailable)) { + $minPriceAvailable = $price; + } + + // Determine lowest overall price + if ($code !== static::CODE_BABYROOM && ($minPriceOverall === 0.0 || $price < $minPriceOverall)) { + $minPriceOverall = $price; + } + + // Determine status + $sourceStatus = $roomRow['status']; + + if (static::STATUS_STOP === $sourceStatus && $price) { + $status = Room::ROOM_STATUS_FULLY_BOOKED; + } elseif (static::STATUS_ON_REQUEST === $sourceStatus) { + $status = Room::ROOM_STATUS_ON_REQUEST; + } else { + $status = Room::ROOM_STATUS_AVAILABLE; + } + + $boardCode = $roomRow['boardCode']; + + $room = [ + 'pid' => $this->pid, + 'date' => 0, + 'hotel' => $hotelUid, + 'bus_pro_id' => (int) $roomRow['busProId'], + 'code' => $code, + 'type' => $roomType, + 'name' => $label, + 'pax' => $pax, + 'nights' => (int) $roomRow['nights'], + 'price' => $price, + 'available' => $available * $pax, + 'status' => $status, + 'services' => serialize($services), + ]; + + $this->db->insert( + 'tx_epproducts_domain_model_room_temp', + $room + ); + + $count++; + } + + $date['board_code'] = $boardCode; + $date['min_price'] = $minPriceAvailable ?: $minPriceOverall; + $date['available'] = $availableTotal; + $date['rooms'] = $count; + $date['min_pax'] = $minPax; + } + + /** + * @param array $services + * @return float + */ + protected function determineDiscount(array $services): float + { + foreach ($services as $key => $section) { + foreach ($section as $service) { + $price = $this->parseFormattedPrice($service['price']); + if ($price < 0) { + return $price; + } + } + } + + return 0.0; + } + + /** + * @param array[] $services a service list of a row + * @param bool $skipWithoutPrice + * @return array + */ + protected function parseService(array $services, bool $skipWithoutPrice = false): array + { + $data = []; + foreach ($services as $service) { + // Skip transportation service direction 'back' + if ($service['direction'] === 'RUECK') { + continue; + } + + // Skip services with status 'Buchungsstop' + if ($service['status'] === static::STATUS_STOP) { + continue; + } + + // Skip services of subcategory 'EIN' + $type = $service['subType']; + if ($type === 'EIN') { + continue; + } + + // Force usage of new types + if ($type === 'SKI' || $type === 'SNO') { + $type = 'VER'; + } + + // A service without a stated price has always been shown as included. + $price = $service['price'] === null ? 0.0 : (float) $service['price']; + + if ($skipWithoutPrice && $price === 0.0) { + continue; + } + + $label = $service['label']; + if ($type === 'BUS') { + $label = static::LABEL_BUS; + } elseif ($type === 'BAH') { + $label = static::LABEL_TRAIN; + } + + if (!array_key_exists($type, $data)) { + $data[$type] = []; + } + + $data[$type][(int) $service['busProId']] = [ + 'idBusPro' => (int) $service['busProId'], + 'label' => $label, + 'price' => number_format($price, 2, ',', '.'), + ]; + } + + ksort($data); + + return $data; + } + + /** + * @param array[] $selectionGroups + * @param int $busProId + * @return array + */ + protected function parseSelectionGroup(array $selectionGroups, int $busProId): array + { + $data = []; + foreach ($selectionGroups as $group) { + if ((int) $group['busProId'] === $busProId) { + $this->addSelections($group['selections'], $data); + } + } + + ksort($data); + + return $data; + } + + /** + * @param array[] $selectionGroups + * @return array + */ + protected function parsePersonnelSelectionGroup(array $selectionGroups): array + { + $data = []; + foreach ($selectionGroups as $group) { + if ($group['label'] !== static::SELECTION_GROUP_LABEL_PERSONNEL) { + continue; + } + $this->logger->warning( + 'selection group matched by label, register its id', + ['label' => $group['label'], 'idbuspro' => (int) $group['busProId']] + ); + $this->addSelections($group['selections'], $data); + } + + ksort($data); + + return $data; + } + + /** + * 'Gruppenkunde' and 'Endkunde' decide. A date carrying neither is judged by + * its travel code. + * + * @param array $row + * @return bool + */ + protected function isGroupTravel(array $row): bool + { + $customer = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_CUSTOMER); + $isGroup = isset($customer[static::SELECTION_CUSTOMER_GROUP]); + $isEnd = isset($customer[static::SELECTION_CUSTOMER_END]); + if ($isGroup || $isEnd) { + return $isGroup; + } + + return !preg_match(static::END_CUSTOMER_CODE_PATTERN, (string) $row['date']['code']); + } + + /** + * Reports dates whose customer selection is missing or contradictory, along + * with what they import as. + * + * @param array $row + */ + protected function checkCustomerSelection(array $row): void + { + $customer = $this->parseSelectionGroup($row['selectionGroups'], static::SELECTION_GROUP_CUSTOMER); + $isGroup = isset($customer[static::SELECTION_CUSTOMER_GROUP]); + $isEnd = isset($customer[static::SELECTION_CUSTOMER_END]); + if ($isGroup !== $isEnd) { + return; + } + + $this->logger->warning( + $isGroup + ? 'date has both Gruppenkunde and Endkunde selected, importing as group travel' + : 'date has neither Gruppenkunde nor Endkunde selected, falling back to travel code, importing as ' + . ($this->isGroupTravel($row) ? 'group travel' : 'end-customer travel'), + ['product' => $row['product']['code'], 'date' => $row['date']['code']] + ); + } + + /** + * @param array[] $selections + * @param array $data + */ + protected function addSelections(array $selections, array &$data): void + { + foreach ($selections as $selection) { + $data[(int) $selection['busProId']] = $selection['label']; + } + } + + /** + * @param array $services + * @param array $date + */ + protected function processBusConditions(array $services, array &$date): void + { + if (!array_key_exists('BUS', $services)) { + return; + } + + foreach ($services['BUS'] as $service) { + $price = $this->parseFormattedPrice($service['price']); + $date['bus_available'] = 1; + $date['bus_price'] = $price; + $date['bus_included'] = (int) ($price === 0.0); + } + } + + /** + * A pickup time is the local date and time as BusPro states it + * ("2027-03-28T02:45:00"), taken over as is. An ISO-8601 time with an + * offset is still accepted and converted to Europe/Berlin, which cannot + * express a time inside the daylight saving gap. + * + * @param string $datetime + * @return string[] date as d.m.Y and time as H:i + */ + protected function parsePickupDateTime(string $datetime): array + { + if (preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::\d{2}(?:\.\d+)?)?$/', $datetime, $parts)) { + return [$parts[3] . '.' . $parts[2] . '.' . $parts[1], $parts[4] . ':' . $parts[5]]; + } + + $pickupDateTime = (new \DateTimeImmutable($datetime))->setTimezone(new \DateTimeZone('Europe/Berlin')); + + return [$pickupDateTime->format('d.m.Y'), $pickupDateTime->format('H:i')]; + } + + /** + * Reads back a price parseService() formatted for display ("1.234,50"). + * + * @param string $price + * @return float + */ + protected function parseFormattedPrice(string $price): float + { + return (float) str_replace(['.', ','], ['', '.'], $price); + } + + protected function getHotelMappings(): void + { + $sql = ' + SELECT u.code code, u.uid uid, h.name name, h.type type, + h.earlybird earlybird, h.new new, h.low_contingent low_contingent, + h.keywords keywords, h.header_title header_title, + h.category category, h.teaser teaser, h.detail_page detail_page + FROM ( + SELECT hotel.code code, hotel.uid uid + FROM tx_epproducts_domain_model_hotel hotel + WHERE hotel.code != "" AND hotel.deleted = 0 AND hotel.hidden = 0 + UNION SELECT matchcode.code code, matchcode.entity uid + FROM tx_epproducts_domain_model_matchcode matchcode + INNER JOIN tx_epproducts_domain_model_hotel matchcode_hotel ON matchcode.entity = matchcode_hotel.uid + WHERE matchcode.deleted = 0 AND matchcode.hidden = 0 + AND matchcode_hotel.deleted = 0 AND matchcode_hotel.hidden = 0 + ORDER BY code + ) u + LEFT JOIN tx_epproducts_domain_model_hotel h ON u.uid = h.uid + '; + $rows = $this->db->fetchAllAssociative($sql); + foreach ($rows as $row) { + $this->hotelMappings[$row['code']] = [ + 'uid' => $row['uid'], + 'code' => $row['code'], + 'type' => $row['type'], + 'name' => $row['name'], + 'earlybird' => $row['earlybird'], + 'new' => $row['new'], + 'low_contingent' => $row['low_contingent'], + 'keywords' => $row['keywords'], + 'header_title' => $row['header_title'], + 'category' => $row['category'], + 'teaser' => $row['teaser'], + 'detail_page' => $row['detail_page'], + ]; + } + } + + /** + * @param string $table + * @param int $uid + * @return string + */ + protected function getTopFactForRecord($table, $uid): string + { + $sql = ' + SELECT f.name + FROM ' . $table . ' mm + INNER JOIN tx_epproducts_domain_model_fact f ON mm.uid_foreign = f.uid + WHERE mm.uid_local = :uid + ORDER BY mm.sorting + LIMIT 0,1 + '; + + return $this->db->fetchOne($sql, ['uid' => $uid]); + } + + /** + * @param array $services + * @param array $list + */ + protected function addServices(array $services, array &$list): void + { + foreach ($services as $key => $section) { + if (!array_key_exists($key, $list)) { + $list[$key] = []; + } + $list[$key] += $section; + } + } + +} diff --git a/packages/ep_products/Classes/Service/DateService.php b/packages/ep_products/Classes/Service/DateService.php new file mode 100644 index 0000000..95e54eb --- /dev/null +++ b/packages/ep_products/Classes/Service/DateService.php @@ -0,0 +1,529 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Date; +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\DateRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Utility\BookingUrlUtility; +use Symfony\Component\OptionsResolver\OptionsResolver; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder; + +class DateService implements SingletonInterface +{ + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @var FilterService + */ + protected $filterService; + + /** + * @var DateRepository + */ + protected $dateRepository; + + /** + * @var UriBuilder + */ + protected $uriBuilder; + + /** + * @var string[] + */ + protected $serviceSections = [ + 'BUS' => 'Anreise', + 'VPF' => 'Verpflegung', + 'SPA' => 'Skipass', + 'KUR' => 'Kurse', + 'VER' => 'Verleih', + 'SON' => 'Sonstiges', + 'LVS' => 'Leihmaterial-Versicherung', + 'PAR' => 'Parkplatz', + ]; + + private $serviceSectionsMapping = [ + 'VE2' => 'VER', + 'VE3' => 'VER', + 'VE4' => 'VER', + 'VE5' => 'VER', + 'VE6' => 'VER', + 'VE7' => 'VER', + 'VE8' => 'VER', + ]; + + /** + * @param ProductRepository $productRepository + * @param DateRepository $dateRepository + * @param FilterService $filterService + * @param UriBuilder $uriBuilder + */ + public function __construct + ( + ProductRepository $productRepository, + DateRepository $dateRepository, + FilterService $filterService, + UriBuilder $uriBuilder + ) + { + $this->productRepository = $productRepository; + $this->filterService = $filterService; + $this->dateRepository = $dateRepository; + $this->uriBuilder = $uriBuilder; + } + + /** + * @param array $options + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function getPriceTable(array $options): array + { + $resolver = new OptionsResolver(); + $this->configurePriceTableOptions($resolver); + $resolvedOptions = $resolver->resolve($options); + + /** @var Product $product */ + $product = $resolvedOptions['product']; + $hotel = $resolvedOptions['hotel']; + $filterSettings = $resolvedOptions['filterSettings']; + $date = $resolvedOptions['date']; + $myEpUrl = $resolvedOptions['myEpUrl']; + $paCode = $resolvedOptions['paCode']; + + if ($filterSettings === null) { + $filterSettings = $this->filterService->getNormalizedFilterSettings(); + } + + $nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true); + + return $this->preprocessPriceTable([ + 'priceTableData' => $this->productRepository->getPricetable($product, $hotel, $filterSettings, $date), + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + 'showBookingButton' => false === $product->getHideBookingButton(), + 'nonBookableDateUids' => $nonBookableDateUids, + ]); + } + + protected function configurePriceTableOptions(OptionsResolver $resolver) + { + $resolver->setRequired(['product', 'hotel', 'myEpUrl']); + $resolver->setDefined(['filterSettings', 'date', 'paCode']); + $resolver->setDefaults([ + 'date' => null, + 'sortByRoom' => true, + ]); + $resolver->setAllowedTypes('product', Product::class); + $resolver->setAllowedTypes('hotel', Hotel::class); + $resolver->setAllowedTypes('date', [Date::class, 'null']); + $resolver->setAllowedTypes('sortByRoom', 'bool'); + } + + public function getDatesTable(array $options): array + { + $resolver = new OptionsResolver(); + $this->configureDatesTableOptions($resolver); + $resolvedOptions = $resolver->resolve($options); + + /** @var Product $product */ + $product = $resolvedOptions['product']; + $hotel = $resolvedOptions['hotel']; + $filterSettings = $resolvedOptions['filterSettings']; + + if ($filterSettings === null) { + $filterSettings = $this->filterService->getNormalizedFilterSettings(); + } + + return $this->productRepository->getAvailableDates($product, $hotel, $filterSettings); + } + + /** + * @param OptionsResolver $resolver + */ + protected function configureDatesTableOptions(OptionsResolver $resolver) + { + $resolver->setDefined(['filterSettings']); + $resolver->setRequired(['product', 'hotel']); + $resolver->setAllowedTypes('product', Product::class); + $resolver->setAllowedTypes('hotel', Hotel::class); + } + + public function getEventPriceTable(Product $product, int $maxPax, string $myEpUrl): array + { + return $this->preprocessEventPriceTable([ + 'priceTableData' => $this->productRepository->getEventPriceTable($product), + 'maxPax' => $maxPax, + 'myEpUrl' => $myEpUrl, + ]); + } + + public function preprocessPriceTable(array $options): array + { + $resolver = new OptionsResolver(); + $resolver->setDefined(['paCode', 'showBookingButton', 'nonBookableDateUids']); + $resolver->setRequired(['priceTableData', 'myEpUrl']); + $resolver->setDefault('showBookingButton', true); + $resolvedOptions = $resolver->resolve($options); + + $priceTable = [ + 'available' => [], + 'unavailable' => [], + ]; + + $priceTableData = $resolvedOptions['priceTableData']; + $myEpUrl = $resolvedOptions['myEpUrl']; + $paCode = $resolvedOptions['paCode']; + $nonBookableDateUids = $resolvedOptions['nonBookableDateUids']; + + foreach ($priceTableData as $row) { + $showBookingButton = $resolvedOptions['showBookingButton'] && !in_array($row['dateBusProId'], $nonBookableDateUids); + + $dateStart = new \DateTime($row['dateStart']); + $dateEnd = new \DateTime($row['dateEnd']); + $bookingUrl = BookingUrlUtility::generateUrl([ + 'dateId' => $row['dateBusProId'], + 'hotelId' => $row['hotelBusProId'], + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + 'returnPageUid' => $row['productDetailPage'] ?? null, + ]); + $optionalServices = $this->preprocessOptionalServices(unserialize($row['roomOptionalServices'], ['allowed_classes' => false])); + + $key = $row['roomAvailable'] && $row['roomPrice'] ? 'available' : 'unavailable'; + + $priceTable[$key][] = [ + 'dateStart' => $dateStart->format('d.m.Y'), + 'dateEnd' => $dateEnd->format('d.m.Y'), + 'season' => $row['season'], + 'summer' => $row['season'] === 's', + 'busIncluded' => (bool) $row['dateBusIncluded'], + 'skiPassIncluded' => (bool) $row['dateSkiPassIncluded'], + 'groupTravel' => (bool) $row['dateGroupTravel'], + 'roomUid' => $row['roomUid'], + 'roomName' => $row['roomName'], + 'available' => $row['roomAvailable'] && $row['roomPrice'], + 'minPrice' => $row['roomPrice'], + 'busPrice' => $row['busPrice'], + 'discount' => $row['roomDiscount'], + 'nights' => $row['roomNights'], + 'optionalServices' => $optionalServices, + 'hasOptionalServices' => count($optionalServices) > 0, + 'dateBusProId' => $row['dateBusProId'], + 'hotelBusProId' => $row['hotelBusProId'], + 'roomBusProId' => $row['roomBusProId'], + 'bookingUrl' => $bookingUrl, + 'showBookingButton' => $showBookingButton, + ]; + } + + return $priceTable; + } + + public function preprocessOptionalServices(array $servicesFlat): array + { + $processed = []; + + foreach ($servicesFlat as $key => $services) { + $servicesWithCosts = array_filter($services, function ($service) { + return '0,00' !== $service['price']; + }); + if (0 === count($servicesWithCosts)) { + continue; + } + if (array_key_exists($key, $this->serviceSectionsMapping)) { + $key = $this->serviceSectionsMapping[$key]; + } + if (!array_key_exists($key, $processed)) { + $processed[$key] = [ + 'label' => $this->serviceSections[$key], + 'services' => [], + ]; + } + $processed[$key]['services'] += $servicesWithCosts; + } + + return $processed; + } + + public function preprocessDatesTable(array $options): array + { + $resolver = new OptionsResolver(); + $resolver->setDefined(['paCode', 'showBookingButton', 'nonBookableDateUids']); + $resolver->setRequired(['dateTableData', 'myEpUrl']); + $resolver->setDefault('showBookingButton', true); + $resolver->setDefault('nonBookableDateUids', []); + $resolvedOptions = $resolver->resolve($options); + + $dateTable = []; + + $dateTableData = $resolvedOptions['dateTableData']; + $myEpUrl = $resolvedOptions['myEpUrl']; + $paCode = $resolvedOptions['paCode']; + $nonBookableDateUids = $resolvedOptions['nonBookableDateUids']; + + foreach ($dateTableData as $row) { + // Default booking button status is determined by the product + $showBookingButton = $resolvedOptions['showBookingButton']; + // Check if booking button is disabled for this date + if (true === in_array($row['dateBusProId'], $nonBookableDateUids)) { + $showBookingButton = false; + } + + $dateStart = new \DateTime($row['dateStart']); + $dateEnd = new \DateTime($row['dateEnd']); + $bookingUrl = BookingUrlUtility::generateUrl([ + 'dateId' => $row['dateBusProId'], + 'hotelId' => $row['hotelBusProId'], + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + 'returnPageUid' => $row['productDetailPage'] ?? null, + ]); + $dateTable[] = [ + 'dateUid' => $row['dateUid'], + 'dateStart' => $dateStart->format('d.m.y'), + 'dateEnd' => $dateEnd->format('d.m.y'), + 'minPrice' => $row['dateMinPrice'], + 'pseudoPrice' => $row['datePseudoPrice'], + 'busPrice' => $row['dateBusPrice'] ?? 0, + 'discount' => $row['dateDiscount'] ?? 0, + 'nights' => $row['dateNights'], + 'busIncluded' => (bool) $row['dateBusIncluded'], + 'skiPassIncluded' => (bool) $row['dateSkiPassIncluded'], + 'groupTravel' => (bool) $row['dateGroupTravel'], + 'servicesIncluded' => unserialize($row['dateServicesIncluded'], ['allowed_classes' => false]), + 'servicesGeneral' => unserialize($row['dateServicesGeneral'], ['allowed_classes' => false]), + 'season' => $row['season'], + 'summer' => $row['season'] === 's', + 'available' => $row['available'], + 'unavailable' => $row['available'] === 0, + 'bookingUrl' => $bookingUrl, + 'showBookingButton' => $showBookingButton, + ]; + } + + return $dateTable; + } + + public function preprocessEventPriceTable(array $options): array + { + $resolver = new OptionsResolver(); + $resolver->setDefined('paCode'); + $resolver->setRequired(['priceTableData', 'myEpUrl']); + $resolver->setDefaults([ + 'maxPax' => 99, + ]); + $resolvedOptions = $resolver->resolve($options); + + $priceTableData = $resolvedOptions['priceTableData']; + $maxPax = $resolvedOptions['maxPax']; + $myEpUrl = $resolvedOptions['myEpUrl']; + $paCode = $resolvedOptions['paCode']; + + $priceTable = [ + Hotel::CATEGORY_STANDARD => [], + Hotel::CATEGORY_COMFORT => [], + Hotel::CATEGORY_DELUXE => [], + Hotel::CATEGORY_SUPER_DELUXE => [], + ]; + + foreach ($priceTableData as $row) + { + // Skip hotels in price table that are not shown as teasers + if (!$row['hotelShowAsTeaser']) { + continue; + } + // Set default hotel category to avoid issues with invalid data + if (0 === $categoryUid = (int)$row['hotelCategory']) { + $categoryUid = Hotel::CATEGORY_STANDARD; + } + $hotelUid = $row['hotelUid']; + + if (!array_key_exists($hotelUid, $priceTable[$categoryUid])) { + + $priceTable[$categoryUid][$hotelUid] = [ + 'category' => $categoryUid, + 'hotelName' => $row['hotelName'], + 'hotelShortName' => $row['hotelShortName'], + 'hotelTitle' => $row['hotelTitle'], + 'hotelDetailPageUid' => $row['hotelMappingDetailPageUid'] ?? $row['hotelDetailPageUid'], + 'bookingUrl' => BookingUrlUtility::generateUrl([ + 'dateId' => $row['dateBusProId'], + 'hotelId' => $row['hotelBusProId'], + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + 'returnPageUid' => $row['productDetailPage'] ?? null, + ]), + 'rooms' => [], + ]; + } + + $roomPax = $row['roomPax']; + if ($roomPax > $maxPax) { + $roomPax = $maxPax; + } + + $entry =& $priceTable[$categoryUid][$hotelUid]['rooms']; + $isUndersubscription = 1 === preg_match('/mit \d{1,2} Personen/', $row['roomName']); + + // Add new entry or replace possible existing entry that is not + // bookable with current record in case of matching category + // and pax + if (!array_key_exists($roomPax, $entry)) { + $entry[$roomPax] = [ + 'price' => $row['roomPrice'], + 'available' => $row['available'] && $row['roomPrice'], + 'undersubscription' => $isUndersubscription, + ] ; + } else { + $lastEntry = $entry[$roomPax]; + if (!$lastEntry['available'] || ($lastEntry['available'] && $row['available'] && ($lastEntry['price'] > $row['roomPrice']))) { + $entry[$roomPax] = [ + 'price' => $row['roomPrice'], + 'available' => $row['available'] && $row['roomPrice'], + 'undersubscription' => $isUndersubscription, + ]; + } + } + } + + // Sort by category + foreach ($priceTable as $category => $rows) + { + uasort($rows, function ($a, $b) { + return strcmp( + $a['rooms'][Hotel::CATEGORY_SUPER_DELUXE]['price'], + $b['rooms'][Hotel::CATEGORY_SUPER_DELUXE]['price'] + ); + }); + $priceTable[$category] = $rows; + } + + return $priceTable; + } + + public function hasIncludedServices(array $tableData): bool + { + foreach ($tableData as $row) { + if ($row['busIncluded'] || $row['skiPassIncluded']) { + return true; + } + } + + return false; + } + + /** + * Group included services across bookable dates. + * + * Returns services that appear in every supplied date and services that + * appear in at least one supplied date but not all of them. + * + * @param array $dates + * @return array{allDates: array, someDates: array} + */ + public function groupIncludedServices(array $dates): array + { + $serviceCounts = []; + $serviceOrder = []; + $dateCount = count($dates); + + foreach ($dates as $date) { + $dateServices = $date['servicesIncluded'] ?? []; + if (!is_array($dateServices)) { + continue; + } + + $seenInDate = []; + foreach ($dateServices as $service) { + if (!is_string($service) || '' === trim($service)) { + continue; + } + + if (isset($seenInDate[$service])) { + continue; + } + $seenInDate[$service] = true; + + if (!array_key_exists($service, $serviceCounts)) { + $serviceCounts[$service] = 0; + $serviceOrder[] = $service; + } + + $serviceCounts[$service]++; + } + } + + $groupedServices = [ + 'allDates' => [], + 'someDates' => [], + ]; + + if (0 === $dateCount) { + return $groupedServices; + } + + foreach ($serviceOrder as $service) { + $count = $serviceCounts[$service] ?? 0; + if ($dateCount === $count) { + $groupedServices['allDates'][] = $service; + } elseif ($count > 0) { + $groupedServices['someDates'][] = $service; + } + } + + sort($groupedServices['someDates'], SORT_NATURAL | SORT_FLAG_CASE); + + return $groupedServices; + } + + public function checkSurcharcheOrDiscount(array $tableData): array + { + $data = [ + 'surcharge' => false, + 'discount' => false, + ]; + + foreach ($tableData as $row) { + if ($row['busPrice'] && $row['busPrice'] > 0) { + $data['surcharge'] = true; + } + if ($row['discount'] && $row['discount'] < 0) { + $data['discount'] = true; + } + } + + return $data; + } +} diff --git a/packages/ep_products/Classes/Service/EmailService.php b/packages/ep_products/Classes/Service/EmailService.php new file mode 100644 index 0000000..94bfda0 --- /dev/null +++ b/packages/ep_products/Classes/Service/EmailService.php @@ -0,0 +1,90 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use Symfony\Component\Mime\Address; +use Symfony\Component\OptionsResolver\OptionsResolver; +use TYPO3\CMS\Core\Mail\FluidEmail; +use TYPO3\CMS\Core\Mail\Mailer; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; + +class EmailService implements SingletonInterface +{ + /** + * @var array + */ + protected $settings; + + public function __construct(ConfigurationManagerInterface $manager) + { + $settings = GeneralUtility::removeDotsFromTS( + $manager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + $this->settings = $settings['plugin']['tx_eptheme']; + } + + /** + * @param array $options + * @param array $variables + * @return bool + */ + public function send(array $options, array $variables) + { + $resolvedOptions = $this->resolveOptions($options); + + /** @var FluidEmail $email */ + $email = GeneralUtility::makeInstance(FluidEmail::class); + $email + ->to(new Address($resolvedOptions['toEmail'], $resolvedOptions['toName'])) + ->from(new Address($resolvedOptions['fromEmail'], $resolvedOptions['fromName'])) + ->subject($resolvedOptions['subject']) + ->setTemplate($resolvedOptions['templateName']) + ->assignMultiple($variables) + ; + + GeneralUtility::makeInstance(Mailer::class)->send($email); + } + + /** + * @param array $options + * @return array + */ + protected function resolveOptions(array $options) + { + $optionsResolver = new OptionsResolver(); + $optionsResolver->setDefined(['toEmail', 'toName', 'subject', 'templateName', 'fromEmail', 'fromName']); + $optionsResolver->setRequired(['toEmail', 'toName', 'subject', 'templateName']); + $optionsResolver->setDefaults([ + 'fromEmail' => $this->settings['settings']['contactFormToEmail'], + 'fromName' => $this->settings['settings']['contactFormToName'], + ]); + + return $optionsResolver->resolve($options); + } +} diff --git a/packages/ep_products/Classes/Service/FilterService.php b/packages/ep_products/Classes/Service/FilterService.php new file mode 100644 index 0000000..ffbfba6 --- /dev/null +++ b/packages/ep_products/Classes/Service/FilterService.php @@ -0,0 +1,342 @@ +setDefaults([ + 'productsPid' => [], + 'priceRanges' => [], + 'countryUids' => [], + 'regionUids' => [], + 'cityUids' => [], + 'conceptUids' => [], + 'boardTypes' => [], + 'hotelTypes' => [], + 'roomTypes' => [], + 'hotelUid' => 0, + 'productUid' => 0, + 'dateFrom' => null, + 'dateTo' => null, + 'nights' => 0, + 'pax' => 0, + 'bus' => null, + ]); + foreach (['hotelUid', 'productUid', 'nights', 'pax'] as $key) { + $optionsResolver->setNormalizer($key, function (Options $options, $value) { + return null !== $value ? (int)$value : null; + }); + } + foreach (['productsPid', 'priceRanges', 'countryUids', 'regionUids', 'cityUids', 'conceptUids', 'boardTypes', 'hotelTypes', 'roomTypes'] as $key) { + $optionsResolver->setNormalizer($key, function (Options $options, $value) { + if (empty($value)) { + return []; + } + return array_map('intval', (array) $value); + }); + } + $optionsResolver->setNormalizer('bus', function (Options $options, $value) { + return null !== $value ? (bool)$value : null; + }); + + $filterSettings = $optionsResolver->resolve($filterSettings); + + if (count($forcedConceptUids) > 0) { + array_push($filterSettings['conceptUids'], ...$forcedConceptUids); + } + + $typoScriptSettings = $this->getTypoScriptSettings(); + if (is_array($typoScriptSettings) && isset($typoScriptSettings['productsPid'])) { + $filterSettings['productsPid'] = GeneralUtility::trimExplode(',', $typoScriptSettings['productsPid'], true); + } + + return $filterSettings; + } + + public function getFilterSettingsFromSearchParams(array $searchParams, array $forcedConceptUids = []): array + { + $filterSettings = []; + + if ($searchParams['destinationType'] && $searchParams['destinationUid']) { + switch ($searchParams['destinationType']) { + case SearchResultService::DESTINATION_TYPE_COUNTRY: + $filterSettings['countryUids'] = [ $searchParams['destinationUid'] ]; + break; + case SearchResultService::DESTINATION_TYPE_REGION: + $filterSettings['regionUids'] = [ $searchParams['destinationUid'] ]; + break; + case SearchResultService::DESTINATION_TYPE_CITY: + $filterSettings['cityUids'] = [ $searchParams['destinationUid'] ]; + break; + case SearchResultService::DESTINATION_TYPE_CONCEPT: + $filterSettings['conceptUids'] = [ $searchParams['destinationUid'] ]; + break; + } + } + if ($searchParams['dateFrom']) { + $filterSettings['dateFrom'] = $searchParams['dateFrom']; + } + if ($searchParams['dateTo']) { + $filterSettings['dateTo'] = $searchParams['dateTo']; + } + if ($searchParams['priceRange']) { + $filterSettings['priceRanges'] = [ $searchParams['priceRange'] ]; + } + if ($searchParams['pax']) { + $filterSettings['pax'] = $searchParams['pax']; + } + if ($searchParams['nights']) { + $filterSettings['nights'] = $searchParams['nights']; + } + + return $this->getNormalizedFilterSettings($filterSettings, $forcedConceptUids); + } + + public function getFilterSettingsFromStaticSearchParams(array $staticSearchParams): array + { + $filterSettings = []; + + if (!empty($staticSearchParams['country'])) { + $filterSettings['countryUids'] = [ $staticSearchParams['country'] ]; + } + if (!empty($staticSearchParams['region'])) { + $filterSettings['regionUids'] = [ $staticSearchParams['region'] ]; + } + if (!empty($staticSearchParams['concept'])) { + $filterSettings['conceptUids'] = GeneralUtility::intExplode(',', $staticSearchParams['concept'], true); + } + if (!empty($staticSearchParams['hotel'])) { + $filterSettings['hotelUid'] = $staticSearchParams['hotel']; + } + if (!empty($staticSearchParams['product'])) { + $filterSettings['productUid'] = $staticSearchParams['product']; + } + if (!empty($staticSearchParams['hotel_types'])) { + $filterSettings['hotelTypes'] = GeneralUtility::intExplode(',', $staticSearchParams['hotel_types'], true); + } + if (!empty($staticSearchParams['bus'])) { + $filterSettings['bus'] = $staticSearchParams['bus']; + } + if (!empty($staticSearchParams['date_from']) && $staticSearchParams['date_from'] !== '0000-00-00') { + $filterSettings['dateFrom'] = $staticSearchParams['date_from']; + } + if (!empty($staticSearchParams['date_to']) && $staticSearchParams['date_to'] !== '0000-00-00') { + $filterSettings['dateTo'] = $staticSearchParams['date_to']; + } + if (!empty($staticSearchParams['pax']) && $staticSearchParams['pax'] > 0) { + $filterSettings['pax'] = $staticSearchParams['pax']; + } + + return $this->getNormalizedFilterSettings($filterSettings); + } + + public function collectFilterOptionsFromSearchResult(SearchResult $searchResult): FilterOptions + { + $priceRanges = []; + $busAvailable = false; + $nights = []; + $concepts = []; + $destinations = []; + $hotelTypes = []; + $roomTypes = []; + $boardTypes = []; + $dateMin = $dateMax = null; + + foreach ($searchResult->getData() as $row) { + $concept = $row['concept']; + if (!array_key_exists($concept['uid'], $concepts)) { + $concepts[$concept['sorting']] = [ + 'uid' => $concept['uid'], + 'name' => $concept['name'], + 'code' => $concept['code'], + ]; + } + foreach ($row['items'] as $item) { + if (null === $dateMin || $item['minDateStart'] < $dateMin) { + $dateMin = $item['minDateStart']; + } + if (null === $dateMax || $item['maxDateEnd'] > $dateMax) { + $dateMax = $item['maxDateEnd']; + } + if ((bool) $item['busAvailable'] === true) { + $busAvailable = true; + } + foreach ($item['nights'] as $nightsOption) { + $duration = (int) $nightsOption <= 4 ? self::DURATION_SHORT : self::DURATION_LONG; + if (!\in_array($duration, $nights, false)) { + $nights[] = $duration; + } + } + $itemDestinations = $item['filterMetadata']['destinations'] ?? [[ + 'country' => $item['country'], + 'region' => $item['region'], + ]]; + foreach ($itemDestinations as $itemDestination) { + $country = $itemDestination['country']; + $region = $itemDestination['region']; + if (!array_key_exists($country['uid'], $destinations)) { + $destinations[$country['uid']] = [ + 'country' => [ + 'uid' => $country['uid'], + 'label' => $country['name'], + 'code' => $country['code'], + ], + 'regions' => [], + ]; + } + if (!array_key_exists($region['uid'], $destinations[$country['uid']]['regions'])) { + $destinations[$country['uid']]['regions'][$region['uid']] = [ + 'uid' => $region['uid'], + 'label' => $region['name'], + ]; + } + } + foreach ($item['filterMetadata']['hotelTypes'] ?? [$item['hotel']['type']] as $hotelType) { + if (!\in_array($hotelType, $hotelTypes, false)) { + $hotelTypes[] = $hotelType; + } + } + foreach ($item['rooms'] as $roomType) { + // Type 0 is not a room (Baby) and must never become a filter option: + // it has no label, so it would render as an empty checkbox and leave + // a hole in the option list. + if ((int) $roomType === Room::ROOM_TYPE_UNCLASSIFIED) { + continue; + } + if (!\in_array($roomType, $roomTypes, false)) { + $roomTypes[] = $roomType; + } + } + foreach ($item['filterMetadata']['boardTypes'] ?? [[ + 'uid' => $item['boardUid'], + 'label' => $item['board'], + ]] as $boardType) { + if (!\in_array($boardType, $boardTypes, false)) { + $boardTypes[] = $boardType; + } + } + foreach (FilterOptions::$priceRangeOptions as $index => $priceRange) { + if (array_key_exists($index, $priceRanges)) { + continue; + } + [$min, $max] = $priceRange; + if ($item['minPrice'] >= $min && $item['minPrice'] < $max) { + $range = FilterOptions::$priceRangeOptions[$index]; + $priceRanges[$index] = [ + 'uid' => $index, + 'min' => $range[0], + 'max' => $range[1], + 'formatted' => $this->formatPriceRange($range[0], $range[1]), + ]; + } + } + } + } + + foreach ($destinations as $key => $value) { + usort($value['regions'], function ($a, $b) { + return strcmp($a['label'], $b['label']); + }); + $destinations[$key] = $value; + } + sort($destinations); + sort($nights); + sort($roomTypes); + ksort($priceRanges); + + usort($boardTypes, function ($a, $b) { + return strcmp($a['label'], $b['label']); + }); + + $filterOptions = new FilterOptions(); + $filterOptions->setPriceRanges($priceRanges); + $filterOptions->setDateFrom($dateMin); + $filterOptions->setDateTo($dateMax); + $filterOptions->setConcepts($concepts); + $filterOptions->setDestinations($destinations); + $filterOptions->setHotelTypes($hotelTypes); + $filterOptions->setRoomTypes($roomTypes); + $filterOptions->setBoardTypes($boardTypes); + $filterOptions->setBusAvailable($busAvailable); + $filterOptions->setNights($nights); + + $filterOptions->setResultCount($searchResult->getTotal()); + + return $filterOptions; + } + + public function formatPriceRange(int $min, int $max): string + { + if ($min === 1) { + return sprintf('bis %d €', $max); + } + if ($max === 999) { + return sprintf('ab %d €', $min); + } + + return sprintf('%d - %d €', $min, $max); + } + + public function getFiltersettingsFromSettings(array $settings): array + { + $filterSettings = []; + + if (isset($settings['regionUid']) && (int)$settings['regionUid'] > 0) { + $filterSettings['regionUids'] = [ $settings['regionUid'] ]; + } elseif (isset($settings['countryUid']) && (int) $settings['countryUid'] > 0) { + $filterSettings['countryUids'] = [ $settings['countryUid'] ]; + } elseif (isset($settings['hotelUid']) && (int) $settings['hotelUid'] > 0) { + $filterSettings['hotelUid'] = $settings['hotelUid']; + } + $conceptUids = GeneralUtility::intExplode(',', $settings['conceptUid'], true); + if ($conceptUids) { + $filterSettings['conceptUids'] = $conceptUids; + } + if (isset($settings['dateFrom']) && (int) $settings['dateFrom'] > 0) { + $dateFrom = new \DateTime(); + $dateFrom->setTimestamp($settings['dateFrom']); + $filterSettings['dateFrom'] = $dateFrom->format('Y-m-d'); + } + if (isset($settings['dateTo']) && (int) $settings['dateTo'] > 0) { + $dateTo = new \DateTime(); + $dateTo->setTimestamp($settings['dateTo']); + $filterSettings['dateTo'] = $dateTo->format('Y-m-d'); + } + if (isset($settings['busAvailable']) && (bool) $settings['busAvailable'] === true) { + $filterSettings['bus'] = true; + } + if (array_key_exists('hotelTypes', $settings)) { + $hotelTypes = GeneralUtility::intExplode(',', $settings['hotelTypes'], true); + $filterSettings['hotelTypes'] = $hotelTypes; + } + + return $this->getNormalizedFilterSettings($filterSettings); + } + + protected function getTypoScriptSettings(): ?array + { + /** @var ConfigurationManager $configurationManager */ + $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); + $settings = $configurationManager + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); + + return $settings ? $settings['plugin.']['tx_epproducts.']['settings.'] : null; + } + +} diff --git a/packages/ep_products/Classes/Service/FilterSettingsEncoder.php b/packages/ep_products/Classes/Service/FilterSettingsEncoder.php new file mode 100644 index 0000000..11f86a0 --- /dev/null +++ b/packages/ep_products/Classes/Service/FilterSettingsEncoder.php @@ -0,0 +1,112 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class FilterSettingsEncoder +{ + /** + * @return array + */ + protected function getMapping() + { + return [ + [ 'countryUids', 'array' ], + [ 'regionUids', 'array' ], + [ 'cityUids', 'array' ], + [ 'conceptUids', 'array' ], + [ 'hotelUid', 'string' ], + [ 'productUid', 'string' ], + [ 'nights', 'string' ], + [ 'pax', 'string' ], + [ 'boardTypes', 'array' ], + [ 'hotelTypes', 'array' ], + [ 'roomTypes', 'array' ], + [ 'dateFrom', 'date' ], + [ 'dateTo', 'date' ], + [ 'bus', 'string' ], + ]; + } + + public function encode(array $filterSettings): string + { + $encoded = []; + foreach ($this->getMapping() as $property) { + [$name, $type] = $property; + if (!isset($filterSettings[$name])) { + $encoded[] = ''; + continue; + } + $value = $filterSettings[$name]; + if ($type === 'array') { + $encoded[] = implode(';', $value); + } elseif ($type === 'date') { + $encoded[] = $value ?? ''; + } else { + $encoded[] = preg_replace('/[\r\n]/', '', $value); + } + } + + return implode('|', $encoded); + } + + /** + * Decodes an encoded filter settings string. + * + * The format is strictly positional, so a string whose field count does not + * match the mapping cannot be interpreted: every field after a missing one + * would be read into the wrong setting. Such input is rejected outright + * rather than silently producing a shifted - and therefore wrong - result. + * + * @param string $data + * @return array the decoded settings, or an empty array if $data is malformed + */ + public function decode(string $data): array + { + $mapping = $this->getMapping(); + $values = explode('|', $data); + + if (count($values) !== count($mapping)) { + return []; + } + + $decoded = []; + foreach ($mapping as $index => $property) + { + [$name, $type] = $property; + $value = $values[$index]; + if ($type === 'array') { + $value = GeneralUtility::trimExplode(';', $value, true); + } + $decoded[$name] = $value; + } + + return $decoded; + } +} diff --git a/packages/ep_products/Classes/Service/GroupsSwissService.php b/packages/ep_products/Classes/Service/GroupsSwissService.php new file mode 100644 index 0000000..4b13942 --- /dev/null +++ b/packages/ep_products/Classes/Service/GroupsSwissService.php @@ -0,0 +1,267 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\BpnConnect\ApiClient; +use EP\EpProducts\Domain\Repository\HotelRepository; +use League\Period\Period; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use TYPO3\CMS\Core\SingletonInterface; + +class GroupsSwissService implements SingletonInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + + /** + * @var HotelRepository + */ + protected $hotelRepository; + + /** + * @var ApiClient + */ + protected $apiClient; + + /** + * @var HttpClient + */ + protected $httpClient; + + /** + * @param HotelRepository $hotelRepository + * @param ApiClient $apiClient + */ + public function __construct(HotelRepository $hotelRepository, ApiClient $apiClient) + { + $this->hotelRepository = $hotelRepository; + $this->apiClient = $apiClient; + $this->httpClient = HttpClient::create(); + } + + public function publishContingents(int $hotelUid, int $houseNumber, string $apiKey): bool + { + $range = Period::after(new \DateTime('now'), '1 year'); + + $hotel = $this->hotelRepository->findByIdentifier($hotelUid); + + if (null === $hotel) { + $this->logger->error(sprintf('Hotel with uid %d not found', $hotelUid)); + return false; + } + + $hotelCode = trim((string) $hotel->getCode()); + if ('' === $hotelCode) { + $this->logger->error(sprintf('Hotel with uid %d has no hotel code', $hotelUid)); + return false; + } + + try { + $calendar = $this->apiClient->getCalendar( + $hotelCode, + \DateTime::createFromImmutable($range->getStartDate()), + \DateTime::createFromImmutable($range->getEndDate()->sub(new \DateInterval('P1D'))) + ); + $calendarRows = isset($calendar['data']) && is_array($calendar['data']) ? $calendar['data'] : []; + } catch (\Throwable $e) { + $this->logger->error(sprintf('No dates found for hotel with uid %d', $hotelUid), ['exception' => $e]); + return false; + } + + $this->resetChart($range, $apiKey, $houseNumber); + + $this->generateChart( + $calendarRows, + $apiKey, + $houseNumber + ); + + return true; + } + + /** + * @param Period $range + * @param string $apiKey + * @param string $houseNumber + */ + protected function resetChart(Period $range, $apiKey, $houseNumber): void + { + $item = [ + 'from' => $range->getStartDate(), + 'to' => $range->getEndDate(), + ]; + + $this->addItem($item, 0, $apiKey, $houseNumber); + } + + /** + * @param array $calendarRows + * @param string $apiKey + * @param int $houseNumber + */ + protected function generateChart(array $calendarRows, $apiKey, $houseNumber): void + { + usort($calendarRows, function (array $left, array $right): int { + return strcmp((string) ($left['date'] ?? ''), (string) ($right['date'] ?? '')); + }); + + $item = null; + $itemStatus = null; + $lastDate = null; + + foreach ($calendarRows as $row) { + if (!isset($row['date'])) { + continue; + } + + $currentDate = new \DateTimeImmutable($row['date']); + $currentStatus = $this->normalizeStatus((string) ($row['status'] ?? 'OK')); + + if (null !== $lastDate && $currentDate > $lastDate->add(new \DateInterval('P1D'))) { + if (null !== $item) { + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); + $item = null; + $itemStatus = null; + } + } + + if ('OK' === $currentStatus) { + if (null !== $item) { + $item['to'] = $lastDate; + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); + $item = null; + $itemStatus = null; + } + $lastDate = $currentDate; + continue; + } + + if (null === $item) { + $item = [ + 'from' => $currentDate, + 'to' => $currentDate, + ]; + $itemStatus = $currentStatus; + } elseif ($itemStatus !== $currentStatus) { + $item['to'] = $lastDate; + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); + $item = [ + 'from' => $currentDate, + 'to' => $currentDate, + ]; + $itemStatus = $currentStatus; + } else { + $item['to'] = $currentDate; + } + + $lastDate = $currentDate; + } + + if (null !== $item) { + $item['to'] = $lastDate; + $this->addItem($item, $this->translateStatusToGroupsSwissState($itemStatus), $apiKey, $houseNumber); + } + } + + /** + * @param array $item + * @param int $status + * @param string $apiKey + * @param int $houseNumber + */ + protected function addItem(array $item, $status, $apiKey, $houseNumber): void + { + $from = $item['from']->format('Y-m-d'); + $to = $item['to']->format('Y-m-d'); + + $this->logger->info(sprintf( + 'Adding status %d in %s-%s for %s', + $status, + $from, + $to, + $houseNumber + )); + + try { + $response = $this + ->httpClient + ->request('GET', 'https://www.groups.swiss/api/1.0/json.php', [ + 'query' => [ + 'apiKey' => $apiKey, + 'method' => 'setAvailabilityChart', + 'house' => $houseNumber, + 'from' => $from, + 'till' => $to, + 'state' => $status, + ], + ]) + ; + $this->logger->info($response->getContent()); + } catch (TransportExceptionInterface $e) { + $this->logger->error(sprintf('A problem occured: %s', $e->getMessage())); + } + } + + /** + * @param string $status + * @return string + */ + protected function normalizeStatus(string $status): string + { + $status = strtoupper(trim($status)); + if ('ONREQUEST' === $status) { + return 'ON_REQUEST'; + } + + if (in_array($status, [ 'OK', 'ON_REQUEST', 'BLOCKED' ], true)) { + return $status; + } + + return 'OK'; + } + + /** + * @param string $status + * @return int + */ + protected function translateStatusToGroupsSwissState(string $status): int + { + $status = $this->normalizeStatus($status); + + if ('ON_REQUEST' === $status) { + return 1; + } + + if ('BLOCKED' === $status) { + return 2; + } + + return 0; + } +} diff --git a/packages/ep_products/Classes/Service/HotelDataService.php b/packages/ep_products/Classes/Service/HotelDataService.php new file mode 100644 index 0000000..29eeba9 --- /dev/null +++ b/packages/ep_products/Classes/Service/HotelDataService.php @@ -0,0 +1,274 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Repository\HotelRepository; +use TYPO3\CMS\Core\SingletonInterface; + +class HotelDataService implements SingletonInterface +{ + + /** + * @var \EP\EpProducts\Domain\Repository\HotelRepository + */ + protected $hotelRepository; + + /** + * @var HotelImageService + */ + protected $hotelImageService; + + /** + * @param HotelRepository $hotelRepository + * @param HotelImageService $hotelImageService + */ + public function __construct(HotelRepository $hotelRepository, HotelImageService $hotelImageService) + { + $this->hotelRepository = $hotelRepository; + $this->hotelImageService = $hotelImageService; + } + + /** + * @param Product $product + * + * @return array + */ + public function getList(Product $product) + { + $hotelData = $this->hotelRepository->getListByProduct($product->getUid()); + return $this->preprocessHotelList($hotelData); + } + + /** + * @param int $hotelUid + * @return array + */ + public function getTeaser($hotelUid) + { + $hotelData = $this->hotelRepository->getSingleByUid($hotelUid); + + if ($hotelData === false) { + $hotelData = $this->hotelRepository->getDateIndependentSingleByUid($hotelUid); + $hotelData['available'] = -1; // TODO: Needs improvement. '-1' actually means: no price/date info available + } + + $hotelCategory = array_key_exists($hotelData['hotelCategory'], Hotel::$categoryLabels) ? + Hotel::$categoryLabels[$hotelData['hotelCategory']] : null; + + $dateStart = new \DateTime($hotelData['dateStart']); + $dateEnd = new \DateTime($hotelData['dateEnd']); + + return [ + 'minDateStart' => $dateStart, + 'maxDateEnd' => $dateEnd, + 'minPrice' => $hotelData['minPrice'], + 'pseudoPrice' => $hotelData['pseudoPrice'], + 'available' => $hotelData['available'], + 'detailPage' => $hotelData['hotelDetailPage'], + 'name' => $hotelData['hotelName'], + 'teaser' => $hotelData['hotelTeaser'], + 'category' => $hotelCategory, + 'images' => $this->hotelImageService->getImages($hotelUid), + 'country' => [ + 'name' => $hotelData['country'], + 'code' => $hotelData['countryCode'], + ], + 'region' => [ + 'name' => $hotelData['regionName'], + ] + ]; + } + + /** + * @param array $criteria + * @return array + */ + public function getTeasersByCriteria(array $criteria) + { + $teaserData = $this->hotelRepository->getListByCriteria($criteria); + + return $this->preprocessTeaserData($teaserData); + } + + /** + * @param array $criteria + * @param array $excludedHotelUids + * @return array + */ + public function getDateIndependentTeasersByCriteria(array $criteria, array $excludedHotelUids = []) + { + $teaserData = $this->hotelRepository->getDateIndependentListByCriteria($criteria, $excludedHotelUids); + + return $this->preprocessTeaserData($teaserData); + } + + /** + * @param array $teaserData + * @return array + */ + protected function preprocessTeaserData(array $teaserData) + { + $teasers = []; + foreach ($teaserData as $row) + { + $hotelUid = $row['hotelUid']; + + $hotelCategory = Hotel::$categoryLabels[$row['hotelCategory']] ?? null; + + if (!array_key_exists($hotelUid, $teasers)) { + $teasers[$hotelUid] = [ + 'minDateStart' => null, + 'maxDateEnd' => null, + 'minPrice' => null, + 'pseudoPrice' => $row['pseudoPrice'], + 'available' => 0, + 'detailPage' => $row['hotelDetailPage'], + 'name' => $row['hotelName'], + 'teaser' => $row['hotelTeaser'], + 'category' => $hotelCategory, + 'images' => $this->hotelImageService->getImages($hotelUid), + 'country' => [ + 'name' => $row['country'], + 'code' => $row['countryCode'], + ], + 'region' => [ + 'name' => $row['regionName'], + ] + ]; + } + + $teaser =& $teasers[$hotelUid]; + $teaser['available'] += $row['available']; + + $dateStart = new \DateTime($row['dateStart']); + $dateEnd = new \DateTime($row['dateEnd']); + + if ($teaser['minDateStart'] === null || $teaser['minDateStart'] > $dateStart) { + $teaser['minDateStart'] = $dateStart; + } + + if ($teaser['maxDateEnd'] === null || $teaser['maxDateEnd'] < $dateEnd) { + $teaser['maxDateEnd'] = $dateEnd; + } + + if ($teaser['minPrice'] === null || $teaser['minPrice'] > $row['minPrice']) { + $teaser['minPrice'] = $row['minPrice']; + } + } + + return $teasers; + } + + /** + * @param array $hotelData + * + * @return array + */ + public function preprocessHotelList(array $hotelData) + { + $hotels = []; + foreach ($hotelData as $row) + { + if (!array_key_exists($row['hotelUid'], $hotels)) { + + $hotelCategory = Hotel::$categoryLabels[$row['hotelCategory']] ?? null; + + $hotels[$row['hotelUid']] = [ + 'hotel' => [ + 'uid' => $row['hotelUid'], + 'name' => $row['hotelName'], + 'teaser' => $row['hotelTeaser'], + 'category' => $hotelCategory, + 'images' => $this->hotelImageService->getImages($row['hotelUid']), + 'fact' => $row['hotelFact'], + ], + 'country' => [ + 'name' => $row['country'], + 'code' => $row['countryCode'], + ], + 'available' => 0, + 'board' => $row['board'], + 'earlybird' => $row['earlybird'], + 'new' => $row['new'], + 'lowContingent' => $row['lowContingent'], + 'servicesIncluded' => unserialize($row['servicesIncluded']), + 'minDateStart' => null, + 'maxDateEnd' => null, + 'minPrice' => null, + 'dates' => [], + 'nights' => [], + ]; + } + + $hotel =& $hotels[$row['hotelUid']]; + + $hotel['available'] += $row['available']; + + if (!\in_array($row['nights'], $hotel['nights'], false)) { + $hotel['nights'][] = $row['nights']; + } + + if (!\in_array($row['dateStart'], $hotel['dates'], false)) { + $hotel['dates'][] = $row['dateStart']; + } + + if ($hotel['minDateStart'] === null + || $hotel['minDateStart'] > $row['dateStart']) { + $hotel['minDateStart'] = $row['dateStart']; + } + + if ($hotel['maxDateEnd'] === null + || $hotel['maxDateEnd'] < $row['dateEnd']) { + $hotel['maxDateEnd'] = $row['dateEnd']; + } + + if ($hotel['minPrice'] === null + || $hotel['minPrice'] > $row['minPrice']) { + $hotel['minPrice'] = $row['minPrice']; + } + } + + uasort($hotels, function ($a, $b) { + // Set price to a very high value to sort hotels + // that are not bookable to end of list + if ($a['minPrice'] === null || !$a['available']) { + $a['minPrice'] = 9999; + } + if ($b['minPrice'] === null || !$b['available']) { + $b['minPrice'] = 9999; + } + + return strcmp($a['minPrice'], $b['minPrice']); + }); + + return $hotels; + } + +} diff --git a/packages/ep_products/Classes/Service/HotelImageService.php b/packages/ep_products/Classes/Service/HotelImageService.php new file mode 100644 index 0000000..7d27fbb --- /dev/null +++ b/packages/ep_products/Classes/Service/HotelImageService.php @@ -0,0 +1,58 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class HotelImageService extends AbstractImageService +{ + /** + * @var string + */ + protected $table = 'tx_epproducts_domain_model_hotel'; + + /** + * @var string + */ + protected $field = 'teaser_images'; + + /** + * @var array + */ + protected $imageSizes = [ + 's' => [ + 'width' => '220', + 'height' => '130c-100', + ], + 'm' => [ + 'width' => '260', + 'height' => '200c-100', + ], + 'l' => [ + 'width' => '280', + 'height' => '220c-100', + ] + ]; +} diff --git a/packages/ep_products/Classes/Service/IcalService.php b/packages/ep_products/Classes/Service/IcalService.php new file mode 100644 index 0000000..324f195 --- /dev/null +++ b/packages/ep_products/Classes/Service/IcalService.php @@ -0,0 +1,159 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\BpnConnect\ApiClient; +use EP\EpProducts\Domain\Model\Hotel; +use Kigkonsult\Icalcreator\Vcalendar; +use TYPO3\CMS\Core\SingletonInterface; + +class IcalService implements SingletonInterface +{ + private const SUMMARY_UNAVAILABLE = 'Unavailable'; + + /** + * @var ApiClient + */ + protected $apiClient; + + /** + * @var Vcalendar + */ + protected $vcalendar; + + /** + * @param ApiClient $apiClient + */ + public function __construct(ApiClient $apiClient) + { + $this->apiClient = $apiClient; + } + + /** + * @param Hotel $hotel + * @return string + */ + public function createContingentsFeed(Hotel $hotel): string + { + $calendarRows = []; + $hotelCode = trim((string) $hotel->getCode()); + + if ($hotelCode !== '') { + $dateFrom = new \DateTimeImmutable('today'); + + try { + $calendar = $this->apiClient->getCalendar( + $hotelCode, + $dateFrom, + null, + ApiClient::CALENDAR_MODE_RANGES + ); + $calendarRows = isset($calendar['data']) && is_array($calendar['data']) ? $calendar['data'] : []; + } catch (\Throwable $e) { + $calendarRows = []; + } + } + + $this->createCalendarInstance(); + $this->generateUnavailableItems($calendarRows); + + try { + return $this->vcalendar->vtimezonePopulate()->createCalendar(); + } catch (\Exception $e) { + return ''; + } + } + + /** + * @param array $calendarRows + */ + protected function generateUnavailableItems(array $calendarRows): void + { + usort($calendarRows, function (array $left, array $right): int { + return strcmp((string) ($left['date_from'] ?? ''), (string) ($right['date_from'] ?? '')); + }); + + foreach ($calendarRows as $row) { + if (!isset($row['date_from'], $row['date_to'])) { + continue; + } + + if (!$this->isUnavailableStatus($row['status'] ?? 'OK')) { + continue; + } + + $dateFrom = new \DateTimeImmutable($row['date_from']); + $dateTo = new \DateTimeImmutable($row['date_to']); + + if ($dateTo < $dateFrom) { + continue; + } + + $this->addBlockedItem([ + 'from' => $dateFrom, + 'to' => $dateTo->add(new \DateInterval('P1D')), + ]); + } + } + + /** + * @param array $item + */ + protected function addBlockedItem(array $item): void + { + try { + $this->vcalendar + ->newVevent() + ->setDtstart($item['from']) + ->setDtend($item['to']) + ->setSummary(self::SUMMARY_UNAVAILABLE) + ; + } catch (\Exception $e) { + } + } + + /** + * @param mixed $status + * @return bool + */ + protected function isUnavailableStatus($status): bool + { + $status = strtoupper(trim((string) $status)); + + return in_array($status, [ 'BLOCKED', 'ON_REQUEST', 'ONREQUEST' ], true); + } + + protected function createCalendarInstance(): void + { + $this->vcalendar = Vcalendar::factory([ Vcalendar::UNIQUE_ID => 'www.ep-reisen.de' ]); + $this->vcalendar->setXprop( + Vcalendar::X_WR_TIMEZONE, + 'Europe/Berlin' + ); + } +} diff --git a/packages/ep_products/Classes/Service/ImportTimestampService.php b/packages/ep_products/Classes/Service/ImportTimestampService.php new file mode 100644 index 0000000..c3bf175 --- /dev/null +++ b/packages/ep_products/Classes/Service/ImportTimestampService.php @@ -0,0 +1,61 @@ +format('d.m.Y H:i:s')); + } + + public function isImportRequired(string $uploadTimestampFile, string $importTimestampFile): bool + { + $updatedTimestampPath = GeneralUtility::getFileAbsFileName($uploadTimestampFile); + + if (!$updatedTimestampPath || !file_exists($updatedTimestampPath)) { + $this->writeImportTimestamp($importTimestampFile); + + return true; + } + + $line = trim(file($updatedTimestampPath)[0]); + + if (empty($line)) { + return true; + } + + $lastUploadAt = \DateTime::createFromFormat('d.m.Y H:i:s', $line); + + $lastImportAt = $this->readImportTimestamp($importTimestampFile); + + if (null === $lastImportAt || $lastUploadAt > $lastImportAt) { + $this->writeImportTimestamp($importTimestampFile); + + return true; + } + + return false; + } + +} diff --git a/packages/ep_products/Classes/Service/LabelService.php b/packages/ep_products/Classes/Service/LabelService.php new file mode 100644 index 0000000..0d52d3a --- /dev/null +++ b/packages/ep_products/Classes/Service/LabelService.php @@ -0,0 +1,106 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Backend\Utility\BackendUtility; + +class LabelService +{ + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getProductLabel(&$parameters, $parentObject) + { + $product = BackendUtility::getRecord('tx_epproducts_domain_model_product', $parameters['row']['uid']); + $parameters['title'] = $product['name_internal']; + if (!empty($product['code'])) { + $parameters['title'] .= ' (' . $product['code'] . ')'; + } + } + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getHotelLabel(&$parameters, $parentObject) + { + $hotel = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $parameters['row']['uid']); + $parameters['title'] = $hotel['name']; + if (!empty($hotel['code'])) { + $parameters['title'] .= ' (' . $hotel['code'] . ')'; + } + } + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getDateLabel(&$parameters, $parentObject) + { + $date = BackendUtility::getRecord('tx_epproducts_domain_model_date', $parameters['row']['uid']); +// $dateFrom = new \DateTime($date['date_start']); +// $dateTo = new \DateTime($date['date_end']); +// $parameters['title'] = sprintf('%s %s (%s)', $date['product_name'], $date['code'], $dateFrom->format('d.m.Y') . ' - ' . $dateTo->format('d.m.Y')); + $parameters['title'] = sprintf('%s (%s)', $date['product_name'], $date['code']); + } + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getOfficetipLabel(&$parameters, $parentObject) + { + $officetip = BackendUtility::getRecord('tx_epproducts_domain_model_officetip', $parameters['row']['uid']); + $author = BackendUtility::getRecord('tx_epproducts_domain_model_teammember', $officetip['author']); + $parameters['title'] = sprintf('%s (%s)', substr($officetip['label'], 0, 30), $author['name']); + } + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getContingentLabel(&$parameters, $parentObject) + { + $contingent = BackendUtility::getRecord('tx_epproducts_domain_model_contingent', $parameters['row']['uid']); + $hotel = BackendUtility::getRecord('tx_epproducts_domain_model_hotel', $contingent['hotel']); + $date = new \DateTime($contingent['date']); + $parameters['title'] = sprintf('%s: %s (%d/%d)', $date->format('d.m.Y'), $hotel['name'], $contingent['available'], $contingent['pax']); + } + + /** + * @param array $parameters + * @param array $parentObject + */ + public function getGroupsPriceConfigLabel(&$parameters, $parentObject) + { + $record = BackendUtility::getRecord('tx_epproducts_domain_model_groupspriceconfig', $parameters['row']['uid']); + $parameters['title'] = sprintf('%s - %s', date('d.m.Y', $record['date_from']), date('d.m.Y', $record['date_to'])); + } +} diff --git a/packages/ep_products/Classes/Service/LocalImportSource.php b/packages/ep_products/Classes/Service/LocalImportSource.php new file mode 100644 index 0000000..f37ffd0 --- /dev/null +++ b/packages/ep_products/Classes/Service/LocalImportSource.php @@ -0,0 +1,56 @@ +isNewImportAvailable()) { + return null; + } + + $path = GeneralUtility::getFileAbsFileName(self::XML_DIR); + $reader = new XmlProductReader( + $path, + GeneralUtility::makeInstance(LogManager::class)->getLogger(XmlProductReader::class) + ); + + return new ProductImport($reader, $reader->getSourceGeneratedAt(), $path); + } + + public function markImported(ProductImport $import): void + { + (new ImportTimestampService())->writeImportTimestamp(self::IMPORT_MARKER); + } + + public function isNewImportAvailable(): bool + { + $timestampService = new ImportTimestampService(); + + $uploadMarkerPath = GeneralUtility::getFileAbsFileName(self::UPLOAD_MARKER); + if ($uploadMarkerPath === '' || !is_file($uploadMarkerPath)) { + // No supplier marker at all: import whatever is in the folder (legacy behaviour). + return true; + } + + $uploadedAt = $timestampService->readImportTimestamp(self::UPLOAD_MARKER); + $lastImportedAt = $timestampService->readImportTimestamp(self::IMPORT_MARKER); + + return $uploadedAt === null || $lastImportedAt === null || $uploadedAt > $lastImportedAt; + } +} diff --git a/packages/ep_products/Classes/Service/LogService.php b/packages/ep_products/Classes/Service/LogService.php new file mode 100644 index 0000000..4e588ce --- /dev/null +++ b/packages/ep_products/Classes/Service/LogService.php @@ -0,0 +1,204 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Dto\FilterOptions; +use EP\EpProducts\Traits\DbConnectionTrait; +use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\SingletonInterface; + +class LogService implements SingletonInterface +{ + use DbConnectionTrait; + + /** + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function dumpSearchLogEpreisen() + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('logs.data as data') + ->addSelectLiteral('DATE_FORMAT(FROM_UNIXTIME(logs.time_micro), "%d.%m.%Y, %H:%i Uhr") as date') + ->from('tx_epproducts_log', 'logs') + ->orderBy('time_micro') + ->execute() + ->fetchAll() + ; + + $csv = [ + [ + 'Datum/Uhrzeit', + 'Destinationstyp', + 'Destinationsname', + 'Konzept', + 'Preisbereich', + 'Personenzahl', + 'Startdatum', + 'Enddatum', + 'Seite', + 'Seiten ID', + ], + ]; + + $labels = $this->fetchEntityLabels(); + + foreach ($result as $row) + { + $data = json_decode(substr($row['data'], 2)); + $searchParams = $data->searchParams; + $priceRange = null; + if ($searchParams->priceRange) { + list ($priceFrom, $priceTo) = FilterOptions::$priceRangeOptions[$searchParams->priceRange]; + $priceRange = sprintf('%s - %s', $priceFrom, $priceTo); + } + $pageTitle = ''; + if ($searchParams->pageUid) { + $page = BackendUtility::getRecord('pages', $searchParams->pageUid); + $pageTitle = $page['title']; + } + $csv[] = [ + $row['date'], + $searchParams->destinationType, + $labels[$searchParams->destinationType][$searchParams->destinationUid], + $labels['concept'][$searchParams->conceptUid], + $priceRange, + $searchParams->pax, + $searchParams->dateFrom, + $searchParams->dateTo, + $pageTitle, + $searchParams->pageUid, + ]; + } + + return $csv; + } + + /** + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function dumpSearchLogEpevents() + { + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('logs.data as data') + ->addSelectLiteral('DATE_FORMAT(FROM_UNIXTIME(logs.time_micro), "%d.%m.%Y, %H:%i Uhr") as date') + ->from('tx_epevents_log', 'logs') + ->orderBy('time_micro') + ->execute() + ->fetchAll() + ; + + $csv = [ + [ + 'Datum/Uhrzeit', + 'Suchbegriff', + 'Seite', + 'Seiten ID', + ], + ]; + + foreach ($result as $row) + { + $data = json_decode(substr($row['data'], 2)); + $pageTitle = ''; + if ($data->pageUid) { + $page = BackendUtility::getRecord('pages', $data->pageUid); + $pageTitle = $page['title']; + } + $csv[] = [ + $row['date'], + $data->search, + $pageTitle, + $data->pageUid, + ]; + } + + return $csv; + } + + /** + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + private function fetchEntityLabels() + { + $labels = [ + 'country' => [], + 'region' => [], + ]; + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('uid', 'name') + ->from('tx_epproducts_domain_model_country', 'c') + ->execute() + ->fetchAll() + ; + + foreach ($result as $row) + { + $labels['country'][$row['uid']] = $row['name']; + } + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('uid', 'name') + ->from('tx_epproducts_domain_model_region') + ->execute() + ->fetchAll() + ; + + foreach ($result as $row) + { + $labels['region'][$row['uid']] = $row['name']; + } + + $qb = $this->getDbConnection()->createQueryBuilder(); + + $result = $qb + ->select('uid', 'name') + ->from('tx_epproducts_domain_model_concept') + ->execute() + ->fetchAll() + ; + + foreach ($result as $row) + { + $labels['concept'][$row['uid']] = $row['name']; + } + + return $labels; + } + +} diff --git a/packages/ep_products/Classes/Service/MyEpImageService.php b/packages/ep_products/Classes/Service/MyEpImageService.php new file mode 100644 index 0000000..59233d8 --- /dev/null +++ b/packages/ep_products/Classes/Service/MyEpImageService.php @@ -0,0 +1,63 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Resource\FileRepository; +use TYPO3\CMS\Extbase\Service\ImageService; + +class MyEpImageService extends AbstractImageService +{ + /** + * @var string + */ + protected $field = 'images'; + + /** + * @var array + */ + protected $imageSizes = [ + 's' => [ + 'width' => '480', + 'height' => '360c-100', + ], + 'm' => [ + 'width' => '768', + 'height' => '576c-100', + ], + 'l' => [ + 'width' => '1280', + 'height' => '960c-100', + ] + ]; + + public function __construct(FileRepository $fileRepository, ImageService $imageService, FrontendInterface $cache, string $table = 'tx_epproducts_domain_model_hotel') + { + parent::__construct($fileRepository, $imageService, $cache); + $this->table = $table; + } +} diff --git a/packages/ep_products/Classes/Service/MyEpUrlResolverService.php b/packages/ep_products/Classes/Service/MyEpUrlResolverService.php new file mode 100644 index 0000000..5b4dc30 --- /dev/null +++ b/packages/ep_products/Classes/Service/MyEpUrlResolverService.php @@ -0,0 +1,55 @@ +getTypoScriptSettings(); + $productionUrl = $this->resolveUrlSetting( + $settings, + 'myEpUrl', + (string) $config->get('ep_products', 'myEpUrl') + ); + $stagingUrl = $this->resolveUrlSetting( + $settings, + 'myEpStagingUrl', + (string) $config->get('ep_products', 'myEpStagingUrl') + ); + + if ('' !== $stagingUrl && !empty($_COOKIE['ep_myep_staging'])) { + return $stagingUrl; + } + + return $productionUrl; + } + + private function getTypoScriptSettings(): array + { + $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); + $settings = GeneralUtility::removeDotsFromTS( + $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + + return $settings['plugin']['tx_epproducts']['settings'] ?? []; + } + + private function resolveUrlSetting(array $settings, string $key, string $fallback): string + { + $value = trim((string) ($settings[$key] ?? '')); + + return $value !== '' ? $value : $fallback; + } +} diff --git a/packages/ep_products/Classes/Service/PickupOverviewService.php b/packages/ep_products/Classes/Service/PickupOverviewService.php new file mode 100644 index 0000000..f05d007 --- /dev/null +++ b/packages/ep_products/Classes/Service/PickupOverviewService.php @@ -0,0 +1,82 @@ +dateRepository = $dateRepository; + } + + /** + * @return array{pickups: array} + */ + public function getOverview(Product $product, ?Hotel $hotel): array + { + $overview = ['pickups' => []]; + if ($hotel === null || $product->isDaytrip()) { + return $overview; + } + + $dates = $this->dateRepository->findPickupsForProductAndHotel((int) $product->getUid(), (int) $hotel->getUid()); + foreach ($dates as $serializedPickups) { + $pickups = $this->readPickups((string) $serializedPickups); + if ($pickups !== []) { + $overview['pickups'] = $pickups; + break; + } + } + + return $overview; + } + + /** + * Keeps the order of the import, which is the boarding order. + */ + private function readPickups(string $serializedPickups): array + { + $datePickups = @unserialize($serializedPickups, ['allowed_classes' => false]); + if (!is_array($datePickups)) { + return []; + } + + $pickups = []; + foreach ($datePickups as $pickup) { + $busProId = (int) ($pickup['bus_pro_id'] ?? 0); + if ($busProId === 0 || isset($pickups[$busProId])) { + continue; + } + $pickups[$busProId] = [ + 'busProId' => $busProId, + 'city' => (string) ($pickup['city'] ?? ''), + 'street' => (string) ($pickup['street'] ?? ''), + // A pickup's price is one leg; the surcharge is split evenly between out- and inbound. + 'price' => 2 * (float) ($pickup['price'] ?? 0.0), + 'time' => (string) ($pickup['time'] ?? ''), + ]; + } + + return array_values($pickups); + } +} diff --git a/packages/ep_products/Classes/Service/ProductDataService.php b/packages/ep_products/Classes/Service/ProductDataService.php new file mode 100644 index 0000000..f1d65b2 --- /dev/null +++ b/packages/ep_products/Classes/Service/ProductDataService.php @@ -0,0 +1,233 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Repository\HotelRepository; +use EP\EpProducts\Domain\Repository\ProductRepository; +use TYPO3\CMS\Core\SingletonInterface; + +class ProductDataService implements SingletonInterface +{ + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @var HotelRepository + */ + protected $hotelRepository; + + /** + * @var ProductImageService + */ + protected $productImageService; + + /** + * @var HotelImageService + */ + protected $hotelImageService; + + /** + * @param ProductRepository $productRepository + * @param HotelRepository $hotelRepository + * @param HotelImageService $hotelImageService + * @param ProductImageService $productImageService + */ + public function __construct( + ProductRepository $productRepository, + HotelRepository $hotelRepository, + HotelImageService $hotelImageService, + ProductImageService $productImageService + ) { + $this->productRepository = $productRepository; + $this->hotelRepository = $hotelRepository; + $this->productImageService = $productImageService; + $this->hotelImageService = $hotelImageService; + } + + /** + * @param array $filterSettings + * + * @return array + */ + public function getTeaser(array $filterSettings): ?array + { + $teaserData = $this->productRepository->getTeasers($filterSettings); + $teasers = $this->preprocessTeasergroup($teaserData); + + if (0 === count($teasers)) { + return null; + } + + return reset($teasers); + } + + /** + * @param array $filterSettings + * @param array $excludedConceptUids + * @param int $limit + * + * @return array + */ + public function getTeasergroup(array $filterSettings, array $excludedConceptUids = [], int $limit = 0): array + { + $teaserData = $this->productRepository->getTeasers($filterSettings, $excludedConceptUids); + $teasers = $this->preprocessTeasergroup($teaserData); + + if ($limit > 0) { + return \array_slice($teasers, 0, $limit); + } + + return $teasers; + } + + /** + * @param array $teaserData + * + * @return array + */ + public function preprocessTeasergroup(array $teaserData): array + { + $data = []; + + foreach ($teaserData as $row) { + if (!array_key_exists($row['productUid'], $data)) { + $data[$row['productUid']] = $this->createTeaserData($row); + } + + $teaser =& $data[$row['productUid']]; + + if (!\in_array($row['nights'], $teaser['nights'], false)) { + $teaser['nights'][] = $row['nights']; + } + + if (!\in_array($row['dateStart'], $teaser['dates'], false)) { + $teaser['dates'][] = $row['dateStart']; + } + + if (!$teaser['skiPassIncluded'] && $row['skiPassIncluded']) { + $teaser['skiPassIncluded'] = true; + } + + if (!$teaser['busIncluded'] && $row['busIncluded']) { + $teaser['busIncluded'] = true; + } + + if ($teaser['minDateStart'] === null || $teaser['minDateStart'] > $row['dateStart']) { + $teaser['minDateStart'] = $row['dateStart']; + } + + if ($teaser['maxDateEnd'] === null || $teaser['maxDateEnd'] < $row['dateEnd']) { + $teaser['maxDateEnd'] = $row['dateEnd']; + } + + if ($teaser['minPrice'] === null || $teaser['minPrice'] > $row['minPrice']) { + $teaser['minPrice'] = $row['minPrice']; + } + + if ($teaser['hotel']['uid'] !== $row['hotelUid']) { + $teaser['hotelCount']++; + } + + if (!array_key_exists($row['hotelCategory'], $teaser['hotelCategories'])) { + $teaser['hotelCategories'][$row['hotelCategory']] = Hotel::$categoryLabels[$row['hotelCategory']]; + ksort($teaser['hotelCategories']); + } + } + + return $data; + } + + /** + * @param array $teaserData + * + * @return array + */ + public function createTeaserData(array $teaserData): array + { + $hotelCategory = Hotel::$categoryLabels[$teaserData['hotelCategory']] ?? null; + + // Use compound key, hotel is not unique + $key = $teaserData['hotelUid'] . ':' . $teaserData['productUid']; + + return [ + 'key' => $key, + 'minDateStart' => null, + 'maxDateEnd' => null, + 'minPrice' => null, + 'hotelCount' => 1, + 'pseudoPrice' => $teaserData['pseudoPrice'], + 'board' => $teaserData['board'], + 'earlybird' => $teaserData['earlybird'], + 'new' => $teaserData['new'], + 'daytrip' => $teaserData['daytrip'], + 'hideBookingButton' => $teaserData['hideBookingButton'], + 'lowContingent' => $teaserData['lowContingent'], + 'servicesIncluded' => unserialize($teaserData['servicesIncluded'], ['allowed_classes' => false]), + 'season' => $teaserData['season'], + 'product' => [ + 'uid' => $teaserData['productUid'], + 'name' => $teaserData['productName'], + 'subline' => $teaserData['productSubline'], + 'detailPage' => $teaserData['productDetailPage'], + 'slug' => $teaserData['productSlug'], + 'fact' => $teaserData['productFact'], + 'feature' => $teaserData['productFeature'], + 'teaser' => $teaserData['productTeaser'], + 'images' => $this->productImageService->getImages($teaserData['productUid']), + ], + 'concept' => [ + 'uid' => $teaserData['conceptUid'], + 'name' => $teaserData['conceptName'], + 'code' => $teaserData['conceptCode'], + 'popupText' => $teaserData['conceptPopupText'], + ], + 'hotel' => [ + 'name' => $teaserData['hotelName'], + 'category' => $hotelCategory, + 'fact' => $teaserData['hotelFact'], + 'uid' => $teaserData['hotelUid'], + 'images' => $this->hotelImageService->getImages($teaserData['hotelUid']), + ], + 'country' => [ + 'name' => $teaserData['countryName'], + 'code' => $teaserData['countryCode'], + ], + 'region' => [ + 'name' => $teaserData['regionName'], + 'fact' => $teaserData['regionFact'], + 'feature' => $teaserData['regionFeature'], + ], + 'dates' => [], + 'nights' => [], + 'hotelCategories' => [], + ]; + } + +} diff --git a/packages/ep_products/Classes/Service/ProductImageService.php b/packages/ep_products/Classes/Service/ProductImageService.php new file mode 100644 index 0000000..f23da27 --- /dev/null +++ b/packages/ep_products/Classes/Service/ProductImageService.php @@ -0,0 +1,66 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class ProductImageService extends AbstractImageService +{ + /** + * @var string + */ + protected $table = 'tx_epproducts_domain_model_product'; + + /** + * @var string + */ + protected $field = 'teaser_images'; + + /** + * @var array + */ + protected $imageSizes = [ + 'xs' => [ + 'width' => '320', + 'height' => '160c-100', + ], + 's' => [ + 'width' => '350', + 'height' => '175c-100', + ], + 'm' => [ + 'width' => '360', + 'height' => '200c-100', + ], + 'l' => [ + 'width' => '370', + 'height' => '220c-100', + ], + 'xl' => [ + 'width' => '500', + 'height' => '250c-100', + ], + ]; +} diff --git a/packages/ep_products/Classes/Service/ProductImport.php b/packages/ep_products/Classes/Service/ProductImport.php new file mode 100644 index 0000000..2cdc0d4 --- /dev/null +++ b/packages/ep_products/Classes/Service/ProductImport.php @@ -0,0 +1,56 @@ + + */ + private $rows; + + /** + * @var \DateTimeImmutable|null + */ + private $sourceGeneratedAt; + + /** + * @var string|null + */ + private $location; + + /** + * @param iterable $rows + * @param \DateTimeImmutable|null $sourceGeneratedAt + * @param string|null $location local directory of an XML export, null otherwise + */ + public function __construct(iterable $rows, ?\DateTimeImmutable $sourceGeneratedAt, ?string $location = null) + { + $this->rows = $rows; + $this->sourceGeneratedAt = $sourceGeneratedAt; + $this->location = $location; + } + + /** + * @return iterable + */ + public function getRows(): iterable + { + return $this->rows; + } + + public function getSourceGeneratedAt(): ?\DateTimeImmutable + { + return $this->sourceGeneratedAt; + } + + public function getLocation(): ?string + { + return $this->location; + } +} diff --git a/packages/ep_products/Classes/Service/ProductImportSource.php b/packages/ep_products/Classes/Service/ProductImportSource.php new file mode 100644 index 0000000..e477a75 --- /dev/null +++ b/packages/ep_products/Classes/Service/ProductImportSource.php @@ -0,0 +1,31 @@ +getMode(); + if ($mode === self::MODE_LOCAL) { + return GeneralUtility::makeInstance(LocalImportSource::class); + } + if ($mode === self::MODE_API) { + return GeneralUtility::makeInstance(ApiImportSource::class); + } + + return GeneralUtility::makeInstance(SftpImportSource::class); + } + + public function getMode(): string + { + $config = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('ep_products'); + $mode = strtolower(trim((string)(is_array($config) ? ($config['productImportSource'] ?? '') : ''))); + + return in_array($mode, [self::MODE_LOCAL, self::MODE_API], true) ? $mode : self::MODE_SFTP; + } +} diff --git a/packages/ep_products/Classes/Service/ResellerDataService.php b/packages/ep_products/Classes/Service/ResellerDataService.php new file mode 100644 index 0000000..5340a2f --- /dev/null +++ b/packages/ep_products/Classes/Service/ResellerDataService.php @@ -0,0 +1,187 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\Product; +use EP\EpProducts\Domain\Model\Reseller; +use EP\EpProducts\Domain\Repository\ProductRepository; +use EP\EpProducts\Domain\Serialization\Date; +use EP\EpProducts\Domain\Serialization\Hotel as SerializedHotel; +use EP\EpProducts\Domain\Serialization\Product as SerializedProduct; +use EP\EpProducts\Domain\Serialization\Room; +use EP\EpProducts\Traits\DbConnectionTrait; +use EP\EpProducts\Utility\BookingUrlUtility; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class ResellerDataService implements SingletonInterface +{ + use DbConnectionTrait; + + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @param ProductRepository $productRepository + */ + public function __construct(ProductRepository $productRepository) + { + $this->productRepository = $productRepository; + } + + /** + * @param Reseller $reseller + * @param string $myEpUrl + * @param array|null $imageProcessingInstructions + * @param string $type + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function preprocessAll(Reseller $reseller, string $myEpUrl, array $imageProcessingInstructions = null, $type = 'facebook') + { + $data = []; + $products = $reseller->getProducts(); + + foreach ($products as $product) { + /** @var Product $product */ + $data['product'][] = $this->preprocessSingle( + $product, + $reseller, + $myEpUrl, + $imageProcessingInstructions, + $type + ); + } + + return $data; + } + + /** + * @param Product $product + * @param Reseller $reseller + * @param string $myEpUrl + * @param array|null $imageProcessingInstructions + * @param string $type + * @return array + * @throws \Doctrine\DBAL\DBALException + */ + public function preprocessSingle( + Product $product, + Reseller $reseller, + string $myEpUrl, + array $imageProcessingInstructions = null, + $type = 'facebook' + ) { + $queryData = 'google' !== $type ? [] : [ + 'utm_source' => 'google', + 'utm_medium' => 'cpc', + 'utm_campaign' => 'dynamisches-remarketing', + 'utm_content' => $product->getCode(), + ]; + + $data = SerializedProduct::fromEntity( + $product, + $imageProcessingInstructions, + $queryData + ); + + $hotels = $product->getHotels(); + $paCode = $reseller->getPaCode(); + + foreach ($hotels as $hotel) { + /** @var Hotel $hotel */ + $hotelData = SerializedHotel::fromEntity($hotel); + $data['hotels']['hotel'][] = $hotelData; + if (false === $product->getBookable()) { + $hotelData['dates']['date'] = []; + } else { + $dates = $this->productRepository->getDatesForExport($product, $hotel); + $hotelData['dates']['date'] = $this->preprocessDates($dates, $myEpUrl, $paCode); + } + } + + return $data; + } + + /** + * @param array $dates + * @param string $myEpUrl + * @param string|null $paCode + * @return array + */ + public function preprocessDates(array $dates, string $myEpUrl, $paCode = null) + { + $data = []; + + foreach ($dates as $row) { + if (!isset($data[$row['dateStart']])) { + $date = Date::fromArray($row); + $data[$row['dateStart']] = $date; + } + + $data[$row['dateStart']]['bookingurl'] = BookingUrlUtility::generateUrl([ + 'dateId' => $row['dateBusProId'], + 'hotelId' => $row['hotelBusProId'], + 'myEpUrl' => $myEpUrl, + 'paCode' => $paCode, + ]); + + $data[$row['dateStart']]['rooms']['room'][] = Room::fromArray($row); + } + + return array_values($data); + } + + /** + * @return bool|array + * @throws \Doctrine\DBAL\DBALException + */ + public function getResellerForCurrentDomain() + { + $currentDomain = GeneralUtility::getIndpEnv('HTTP_HOST'); + + $qb = $this->getDbConnection()->createQueryBuilder(); + + return $qb + ->select('r.name as name', 'r.pa_code as paCode') + ->from('tx_epproducts_domain_model_reseller', 'r') + ->innerJoin('r', 'sys_domain', 'd', 'r.uid = d.tx_epproducts_reseller') + ->where('d.domainName = :domain') + ->andWhere('r.deleted = 0') + ->andWhere('r.hidden = 0') + ->andWhere('d.hidden = 0') + ->setParameter('domain', $currentDomain) + ->execute() + ->fetch() + ; + } + +} diff --git a/packages/ep_products/Classes/Service/RoomTypeResolver.php b/packages/ep_products/Classes/Service/RoomTypeResolver.php new file mode 100644 index 0000000..cefcec8 --- /dev/null +++ b/packages/ep_products/Classes/Service/RoomTypeResolver.php @@ -0,0 +1,129 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Room; + +/** + * Derives the room type from the data each element carries. + * + * Replaces the former tx_epproducts_domain_model_roommapping lookup: zimmercode is + * neither unique per room nor a function of room type, so a code-keyed table cannot + * classify this data. MaxPax is the advertised occupancy and is present on every row. + */ +class RoomTypeResolver +{ + /** + * Phrases in zimmertext that mark a bed shared with other customers. + * + * @var array + */ + const LABEL_PATTERNS_WITH_OTHERS = [ + 'bett im', + 'mit ander', + ]; + + /** + * Phrases in zimmertext that mark a deliberate single use of a larger room. + * + * @var array + */ + const LABEL_PATTERNS_SINGLE_USE = [ + 'einzel', + '1 person', + ]; + + /** + * @param string $code zimmercode + * @param string $label zimmertext + * @param int $maxPax MaxPax + * @param int $minPax MinPax + * @return int one of the Room::ROOM_TYPE_* constants + */ + public function resolve(string $code, string $label, int $maxPax, int $minPax): int + { + // Not a room: "Babypreis für 0-2 Jährige". Kept in the date record for its + // price and availability, but hidden from the filter. + if ($code === DateImportService::CODE_BABYROOM) { + return Room::ROOM_TYPE_UNCLASSIFIED; + } + + if ($this->matchesAny($label, static::LABEL_PATTERNS_WITH_OTHERS)) { + return Room::ROOM_TYPE_WITH_OTHERS; + } + + $pax = $maxPax > 0 ? $maxPax : $minPax; + + if ($pax >= 1) { + // Type numbering is chosen so that type === pax for pax 1-8. + return $pax >= 9 ? Room::ROOM_TYPE_FOR_NINE_PLUS : $pax; + } + + // No usable occupancy - should never happen on current data, the caller logs it. + return Room::ROOM_TYPE_WITH_OTHERS; + } + + /** + * Tripwire for an under-matching label pattern: a row classified as a single room + * whose label carries no single-use marker is most likely a shared bed phrased in + * a way LABEL_PATTERNS_WITH_OTHERS does not yet cover. + * + * @param string $code zimmercode + * @param string $label zimmertext + * @param int $maxPax MaxPax + * @return bool + */ + public function needsReview(string $code, string $label, int $maxPax): bool + { + if ($this->resolve($code, $label, $maxPax, $maxPax) !== Room::ROOM_TYPE_FOR_ONE) { + return false; + } + + if ($maxPax !== 1) { + return false; + } + + return !$this->matchesAny($label, static::LABEL_PATTERNS_SINGLE_USE); + } + + /** + * @param string $label + * @param array $patterns + * @return bool + */ + protected function matchesAny(string $label, array $patterns): bool + { + foreach ($patterns as $pattern) { + if (mb_strpos(mb_strtolower($label), mb_strtolower($pattern)) !== false) { + return true; + } + } + + return false; + } +} diff --git a/packages/ep_products/Classes/Service/SearchResultService.php b/packages/ep_products/Classes/Service/SearchResultService.php new file mode 100644 index 0000000..768097c --- /dev/null +++ b/packages/ep_products/Classes/Service/SearchResultService.php @@ -0,0 +1,287 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Hotel; +use EP\EpProducts\Domain\Model\SearchResult; +use EP\EpProducts\Domain\Repository\ProductRepository; +use Symfony\Component\OptionsResolver\OptionsResolver; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +class SearchResultService implements SingletonInterface +{ + public const DESTINATION_TYPE_COUNTRY = 'country'; + public const DESTINATION_TYPE_REGION = 'region'; + public const DESTINATION_TYPE_CITY = 'city'; + public const DESTINATION_TYPE_CONCEPT = 'concept'; + + /** + * @var ProductRepository + */ + protected $productRepository; + + /** + * @var HotelImageService + */ + protected $hotelImageService; + + /** + * @var ProductImageService + */ + protected $productImageService; + + /** + * @param ProductRepository $productRepository + * @param HotelImageService $hotelImageService + * @param ProductImageService $productImageService + */ + public function __construct( + ProductRepository $productRepository, + HotelImageService $hotelImageService, + ProductImageService $productImageService + ) + { + $this->productRepository = $productRepository; + $this->hotelImageService = $hotelImageService; + $this->productImageService = $productImageService; + } + + public function getNormalizedSearchParams(array $searchParams = []): array + { + $optionsResolver = new OptionsResolver(); + $optionsResolver->setDefaults([ + 'priceRange' => null, + 'dateFrom' => null, + 'dateTo' => null, + 'destinationUid' => null, + 'destinationType' => null, + 'nights' => null, + 'pax' => 0, + 'pageUid' => null, + ]); + + return $optionsResolver->resolve($searchParams); + } + + public function getResult(array $filterSettings, array $excludedConceptUids = []): SearchResult + { + $searchResultData = $this->productRepository->getSearchResult($filterSettings, $excludedConceptUids); + + return $this->preprocess($searchResultData, true); + } + + public function preprocess(array $searchResultData, bool $aggregateEvents = false): SearchResult + { + $data = []; + $total = 0; + + foreach ($searchResultData as $row) { + // Concepts + if (!array_key_exists($row['conceptUid'], $data)) { + $data[$row['conceptUid']] = [ + 'concept' => [ + 'uid' => $row['conceptUid'], + 'name' => $row['conceptName'], + 'code' => $row['conceptCode'], + 'sorting' => $row['conceptSorting'], + 'popupText' => $row['conceptPopupText'], + ], + 'items' => [], + ]; + } + + $items =& $data[$row['conceptUid']]['items']; + + $isAggregatedEvent = $aggregateEvents && 'evt' === $row['conceptCode']; + + // Events are product-level search results. Other results (including + // watchlist items) remain tied to a particular hotel. + $key = $isAggregatedEvent + ? (string) $row['productUid'] + : $row['hotelUid'] . ':' . $row['productUid']; + + $hotelCategory = Hotel::$categoryLabels[$row['hotelCategory']] ?? null; + + if (!array_key_exists($key, $items)) { + $items[$key] = [ + 'key' => $key, + 'minDateStart' => null, + 'maxDateEnd' => null, + 'minPrice' => null, + 'detailPageUri' => null, + 'earlybird' => $row['earlybird'], + 'daytrip' => $row['daytrip'], + 'season' => $row['season'], + 'hideBookingButton' => $row['hideBookingButton'], + 'busAvailable' => $row['busAvailable'], + 'available' => $row['available'], + 'lowContingent' => $row['lowContingent'], + 'servicesIncluded' => unserialize($row['servicesIncluded'], ['allowed_classes' => false]), + 'hotel' => [ + 'uid' => $row['hotelUid'], + 'name' => $row['hotelName'], + 'teaser' => $row['hotelTeaser'], + 'images' => $this->hotelImageService->getImages($row['hotelUid']), + 'fact' => $row['hotelFact'], + 'category' => $hotelCategory, + 'type' => $row['hotelType'], + ], + 'country' => [ + 'uid' => $row['countryUid'], + 'code' => $row['countryCode'], + 'name' => $row['countryName'], + ], + 'region' => [ + 'uid' => $row['regionUid'], + 'name' => $row['regionName'], + 'fact' => $row['regionFact'], + 'feature' => $row['regionFeature'], + ], + 'product' => [ + 'uid' => $row['productUid'], + 'detailPage' => $row['productDetailPage'], + 'name' => $row['productName'], + 'subline' => $row['productSubline'], + 'feature' => $row['productFeature'], + 'fact' => $row['productFact'], + 'images' => $isAggregatedEvent + ? $this->productImageService->getImages($row['productUid']) + : [], + ], + 'board' => $row['board'], + 'boardUid' => $row['boardUid'], + 'dates' => [], + 'nights' => [], + 'teaserNights' => [], + 'rooms' => [], + 'new' => $row['new'], + 'skiPassIncluded' => $row['skiPassIncluded'], + 'filterMetadata' => [ + 'destinations' => [], + 'hotelTypes' => [], + 'boardTypes' => [], + ], + ]; + $total++; + } + + $item =& $items[$key]; + + $destination = [ + 'country' => [ + 'uid' => $row['countryUid'], + 'code' => $row['countryCode'], + 'name' => $row['countryName'], + ], + 'region' => [ + 'uid' => $row['regionUid'], + 'name' => $row['regionName'], + 'fact' => $row['regionFact'], + 'feature' => $row['regionFeature'], + ], + ]; + if (!\in_array($destination, $item['filterMetadata']['destinations'], false)) { + $item['filterMetadata']['destinations'][] = $destination; + } + $hotelType = $row['hotelType'] ?? $item['hotel']['type']; + if (!\in_array($hotelType, $item['filterMetadata']['hotelTypes'], false)) { + $item['filterMetadata']['hotelTypes'][] = $hotelType; + } + $boardType = ['uid' => $row['boardUid'] ?? null, 'label' => $row['board']]; + if (!\in_array($boardType, $item['filterMetadata']['boardTypes'], false)) { + $item['filterMetadata']['boardTypes'][] = $boardType; + } + + foreach (GeneralUtility::trimExplode(',', (string) ($row['roomTypes'] ?? ''), true) as $roomType) { + if (!\in_array($roomType, $item['rooms'], false)) { + $item['rooms'][] = $roomType; + } + } + + if (!\in_array($row['nights'], $item['nights'], false)) { + $item['nights'][] = $row['nights']; + } + + if (!\in_array($row['dateStart'], $item['dates'], false)) { + $item['dates'][] = $row['dateStart']; + } + + $dateStart = $row['dateStart']; + $dateEnd = $row['dateEnd']; + + if ($item['minDateStart'] === null + || $item['minDateStart'] > $dateStart) { + $item['minDateStart'] = $dateStart; + } + + if ($item['maxDateEnd'] === null + || $item['maxDateEnd'] < $dateEnd) { + $item['maxDateEnd'] = $dateEnd; + } + + if ($item['minPrice'] === null + || $item['minPrice'] > $row['minPrice']) { + $item['minPrice'] = $row['minPrice']; + // Teaser-specific offer data must describe the cheapest row. + $item['teaserNights'] = [$row['nights']]; + $item['board'] = $row['board']; + $item['boardUid'] = $row['boardUid']; + $item['daytrip'] = $row['daytrip']; + $item['season'] = $row['season']; + $item['hideBookingButton'] = $row['hideBookingButton']; + $item['servicesIncluded'] = unserialize($row['servicesIncluded'], ['allowed_classes' => false]); + $item['new'] = $row['new']; + $item['skiPassIncluded'] = $row['skiPassIncluded']; + } + + // Availability indicators describe the consolidated set, not only + // the row that supplies the teaser price. + $item['earlybird'] = (bool) $item['earlybird'] || (bool) $row['earlybird']; + $item['busAvailable'] = (bool) $item['busAvailable'] || (bool) $row['busAvailable']; + $item['lowContingent'] = (bool) $item['lowContingent'] || (bool) $row['lowContingent']; + $item['available'] = (bool) $item['available'] || (bool) $row['available']; + + } + + // Sort products by minprice within concept sections except for 'event' concept + foreach ($data as &$section) { + if ('evt' === $section['concept']['code']) { + continue; + } + + uasort($section['items'], function ($a, $b) { + return strcmp($a['minPrice'], $b['minPrice']); + }); + } + + // Turn result data into simple array to ensure order in js + $result = array_values($data); + + return new SearchResult($result, $total); + } +} diff --git a/packages/ep_products/Classes/Service/SftpImportException.php b/packages/ep_products/Classes/Service/SftpImportException.php new file mode 100644 index 0000000..3b6c51c --- /dev/null +++ b/packages/ep_products/Classes/Service/SftpImportException.php @@ -0,0 +1,7 @@ +fetchIfNewer(); + if ($path === null) { + return null; + } + + $reader = new XmlProductReader($path, $this->logger); + + return new ProductImport($reader, $reader->getSourceGeneratedAt(), $path); + } + + /** + * {@see ProductImportSource::markImported()} — publish the download as the new + * change-detection baseline (only reached after a successful import), then + * refresh the display-only marker for the BE module and system-info toolbar. + */ + public function markImported(ProductImport $import): void + { + $this->commit((string)$import->getLocation()); + (new ImportTimestampService())->writeImportTimestamp(self::IMPORT_MARKER); + } + + /** + * {@see ProductImportSource::isNewImportAvailable()} — alias for {@see isRemoteNewer()}. + */ + public function isNewImportAvailable(): bool + { + return $this->isRemoteNewer(); + } + + /** + * Timestamp of the export currently offered on the remote server, or null when + * the marker is absent/unreadable/unparseable (supplier upload not finished). + */ + public function getRemoteUploadTimestamp(): ?\DateTime + { + $config = $this->buildConfig(); + $sftp = $this->connect($config); + try { + [, $markerBytes] = $this->readRemoteMarker($sftp, $config['remotePath']); + } finally { + $sftp->disconnect(); + } + + return $markerBytes !== null ? $this->parseMarkerTimestamp($markerBytes) : null; + } + + /** + * Timestamp of the export we last successfully imported (staged marker copy). + */ + public function getStagedImportedTimestamp(): ?\DateTime + { + $marker = $this->stagingBasePath() . '/' . self::PUBLISHED_DIR . '/' . self::MARKER_FILE; + if (!is_file($marker)) { + return null; + } + + return $this->parseMarkerTimestamp((string)file_get_contents($marker)); + } + + /** + * True when the remote export is newer than the last imported one (or nothing + * has been imported yet). False when there is nothing to do. + */ + public function isRemoteNewer(): bool + { + $remoteTs = $this->getRemoteUploadTimestamp(); + if ($remoteTs === null) { + return false; + } + + $stagedTs = $this->getStagedImportedTimestamp(); + + return $stagedTs === null || $remoteTs > $stagedTs; + } + + /** + * Freshness check + (only when newer) full download into a fresh temp dir. + * Returns the temp-dir path — NOT yet published. The caller runs + * DateImportService::import() against it and calls {@see commit()} on success. + * Returns null when the remote export is not newer / not ready. + * + * @throws SftpImportException on connect/auth/host-key/partial-transfer failure + */ + public function fetchIfNewer(): ?string + { + $this->cleanupStale(); + $config = $this->buildConfig(); + $sftp = $this->connect($config); + + try { + [$remoteDir, $markerBytes] = $this->readRemoteMarker($sftp, $config['remotePath']); + $remoteTs = $markerBytes !== null ? $this->parseMarkerTimestamp($markerBytes) : null; + + if ($remoteTs === null) { + $this->logWarning('remote upload marker missing or unparseable; skipping product import', [ + 'host' => $config['host'], + 'remotePath' => $config['remotePath'], + ]); + + return null; + } + + $stagedTs = $this->getStagedImportedTimestamp(); + if ($stagedTs !== null && $remoteTs <= $stagedTs) { + return null; + } + + return $this->downloadInto($sftp, $remoteDir, $markerBytes); + } finally { + $sftp->disconnect(); + } + } + + /** + * Unconditional download into a fresh temp dir. Returns the temp-dir path. + * + * @throws SftpImportException + */ + public function fetch(): string + { + $this->cleanupStale(); + $config = $this->buildConfig(); + $sftp = $this->connect($config); + + try { + [$remoteDir, $markerBytes] = $this->readRemoteMarker($sftp, $config['remotePath']); + if ($markerBytes === null) { + throw new SftpImportException( + 'Remote export has no readable ' . self::MARKER_FILE . ' under "' . $config['remotePath'] . '"' + ); + } + + return $this->downloadInto($sftp, $remoteDir, $markerBytes); + } finally { + $sftp->disconnect(); + } + } + + /** + * Publish a temp dir produced by {@see fetchIfNewer()} / {@see fetch()} to + * .../current, making its marker the new change-detection baseline. Call only + * after import() succeeded. + * + * @throws SftpImportException + */ + public function commit(string $stagedPath): string + { + $base = $this->stagingBasePath(); + $stagedPath = rtrim($stagedPath, '/'); + + if (strpos($stagedPath . '/', $base . '/') !== 0 || !is_dir($stagedPath)) { + throw new SftpImportException('Refusing to publish unknown staging directory: ' . $stagedPath); + } + + $published = $base . '/' . self::PUBLISHED_DIR; + + if (is_dir($published)) { + $retired = $published . '.old-' . time() . '-' . bin2hex(random_bytes(3)); + if (!@rename($published, $retired)) { + throw new SftpImportException('Cannot move previous import aside: ' . $published); + } + } + + if (!@rename($stagedPath, $published)) { + throw new SftpImportException('Cannot publish staging directory to ' . $published); + } + + foreach ((array)glob($base . '/' . self::PUBLISHED_DIR . '.old-*', GLOB_ONLYDIR) as $old) { + GeneralUtility::rmdir($old, true); + } + + return $published; + } + + /** + * @param array{host:string,port:int,user:string,password:string,remotePath:string,timeout:int,hostKey:string} $config + */ + private function connect(array $config): SFTP + { + try { + $sftp = new SFTP($config['host'], $config['port'], $config['timeout']); + } catch (\Throwable $e) { + throw new SftpImportException( + sprintf('Cannot connect to SFTP host %s:%d: %s', $config['host'], $config['port'], $e->getMessage()), + 0, + $e + ); + } + + if ($config['hostKey'] !== '') { + $this->verifyHostKey($sftp, $config['host'], $config['hostKey']); + } + + try { + $authenticated = $sftp->login($config['user'], $config['password']); + } catch (\Throwable $e) { + throw new SftpImportException( + sprintf('Cannot connect to SFTP host %s:%d: %s', $config['host'], $config['port'], $e->getMessage()), + 0, + $e + ); + } + + if ($authenticated !== true) { + throw new SftpImportException(sprintf('SFTP authentication failed for user "%s"', $config['user'])); + } + + return $sftp; + } + + private function verifyHostKey(SFTP $sftp, string $host, string $expected): void + { + try { + $actual = $sftp->getServerPublicHostKey(); + } catch (\Throwable $e) { + throw new SftpImportException('Cannot read SSH host key for ' . $host . ': ' . $e->getMessage(), 0, $e); + } + + if ($actual === false) { + throw new SftpImportException('Cannot read SSH host key for ' . $host); + } + + $expected = trim($expected); + $actual = trim($actual); + + if (hash_equals($actual, $expected)) { + return; + } + + $parts = explode(' ', $actual); + $rawKey = isset($parts[1]) ? base64_decode($parts[1], true) : false; + if ($rawKey !== false) { + $fingerprint = 'SHA256:' . rtrim(base64_encode(hash('sha256', $rawKey, true)), '='); + if (hash_equals($fingerprint, $expected)) { + return; + } + } + + throw new SftpImportException('SSH host key mismatch for ' . $host . ' — refusing to connect'); + } + + /** + * Reads the upload marker, probing each acceptable form of the configured + * remote directory. Some SFTP servers (e.g. Hetzner Storage Box) only accept + * paths relative to the login directory and silently fail on a leading slash. + * + * @return array{0:string,1:?string} the directory form that worked, and the marker bytes (null if none did) + */ + private function readRemoteMarker(SFTP $sftp, string $configuredPath): array + { + $candidates = $this->remoteDirCandidates($configuredPath); + + foreach ($candidates as $dir) { + try { + $contents = $sftp->get($this->joinRemote($dir, self::MARKER_FILE)); + } catch (\Throwable $e) { + throw new SftpImportException( + 'Failed to read remote ' . self::MARKER_FILE . ': ' . $e->getMessage(), + 0, + $e + ); + } + + if ($contents !== false) { + return [$dir, (string)$contents]; + } + } + + return [$candidates[0], null]; + } + + /** + * @return string[] non-empty, unique + */ + private function remoteDirCandidates(string $configuredPath): array + { + $configured = trim($configuredPath); + $relative = ltrim($configured, '/'); + + $candidates = []; + if ($configured !== '' && $configured !== '/') { + $candidates[] = $configured; + } + if ($relative !== '' && $relative !== $configured) { + $candidates[] = $relative; + } + $candidates[] = '.'; + + return array_values(array_unique($candidates)); + } + + /** + * Download Ziel_*.xml + zustiege.xml + uebertragung.info into a fresh temp dir, + * verifying each file's size against the remote listing. The marker is written + * last, from the bytes already fetched for the freshness check. + * + * @throws SftpImportException + */ + private function downloadInto(SFTP $sftp, string $remotePath, string $markerBytes): string + { + $list = $sftp->rawlist($remotePath, false); + if ($list === false) { + throw new SftpImportException('Cannot list remote directory ' . $remotePath); + } + + $productFiles = []; + $pickupSize = null; + foreach ($list as $name => $attrs) { + if ($name === '.' || $name === '..' || !is_array($attrs)) { + continue; + } + if ((int)($attrs['type'] ?? 0) === self::REMOTE_TYPE_DIRECTORY) { + continue; + } + if (strpos($name, self::PRODUCT_PREFIX) === 0 && substr($name, -4) === '.xml') { + $productFiles[$name] = (int)($attrs['size'] ?? -1); + } elseif ($name === self::PICKUPS_FILE) { + $pickupSize = (int)($attrs['size'] ?? -1); + } + } + + if (count($productFiles) === 0) { + throw new SftpImportException('Remote export contains no ' . self::PRODUCT_PREFIX . '_*.xml files'); + } + + $previousCount = $this->stagedProductFileCount(); + if ($previousCount > 0 && count($productFiles) < (int)floor($previousCount * 0.5)) { + throw new SftpImportException(sprintf( + 'Remote export looks truncated (%d %s_*.xml files vs %d previously) — refusing to import', + count($productFiles), + self::PRODUCT_PREFIX, + $previousCount + )); + } + + $toDownload = $productFiles; + if ($pickupSize !== null) { + $toDownload[self::PICKUPS_FILE] = $pickupSize; + } else { + $this->logWarning('remote export has no ' . self::PICKUPS_FILE . '; pickups will be skipped'); + } + + $tmp = $this->newTempDir(); + + foreach ($toDownload as $name => $expectedSize) { + $target = $tmp . '/' . $name; + try { + $ok = $sftp->get($this->joinRemote($remotePath, $name), $target); + } catch (\Throwable $e) { + GeneralUtility::rmdir($tmp, true); + throw new SftpImportException('Download failed for ' . $name . ': ' . $e->getMessage(), 0, $e); + } + + if ($ok === false || !is_file($target)) { + GeneralUtility::rmdir($tmp, true); + throw new SftpImportException('Download failed for ' . $name); + } + + clearstatcache(true, $target); + if ($expectedSize >= 0 && filesize($target) !== $expectedSize) { + $actualSize = filesize($target); + GeneralUtility::rmdir($tmp, true); + throw new SftpImportException(sprintf( + 'Size mismatch for %s (got %d bytes, expected %d)', + $name, + $actualSize, + $expectedSize + )); + } + } + + // Write the completion marker last, mirroring the supplier's upload order. + GeneralUtility::writeFile($tmp . '/' . self::MARKER_FILE, $markerBytes, false); + + return $tmp; + } + + private function stagedProductFileCount(): int + { + $dir = $this->stagingBasePath() . '/' . self::PUBLISHED_DIR; + if (!is_dir($dir)) { + return 0; + } + + $count = 0; + foreach ((array)scandir($dir) as $name) { + if (strpos((string)$name, self::PRODUCT_PREFIX) === 0 && substr((string)$name, -4) === '.xml') { + $count++; + } + } + + return $count; + } + + private function parseMarkerTimestamp(string $contents): ?\DateTime + { + $line = trim((string)strtok($contents, "\r\n")); + if ($line === '') { + return null; + } + + $date = \DateTime::createFromFormat(self::MARKER_TS_FORMAT, $line); + + return $date instanceof \DateTime ? $date : null; + } + + private function logWarning(string $message, array $context = []): void + { + if ($this->logger !== null) { + $this->logger->warning($message, $context); + } + } + + private function stagingBasePath(): string + { + return Environment::getVarPath() . '/transient/' . self::STAGING_SUBDIR; + } + + private function newTempDir(): string + { + $dir = $this->stagingBasePath() . '/.tmp-' . getmypid() . '-' . bin2hex(random_bytes(4)); + GeneralUtility::mkdir_deep($dir); + if (!is_dir($dir) || !is_writable($dir)) { + throw new SftpImportException('Cannot create staging directory: ' . $dir); + } + + return $dir; + } + + private function cleanupStale(): void + { + $base = $this->stagingBasePath(); + if (!is_dir($base)) { + return; + } + + $stale = array_merge( + (array)glob($base . '/.tmp-*', GLOB_ONLYDIR), + (array)glob($base . '/' . self::PUBLISHED_DIR . '.old-*', GLOB_ONLYDIR) + ); + foreach ($stale as $dir) { + GeneralUtility::rmdir($dir, true); + } + } + + private function joinRemote(string $base, string $name): string + { + return rtrim($base, '/') . '/' . ltrim($name, '/'); + } + + /** + * @return array{host:string,port:int,user:string,password:string,remotePath:string,timeout:int,hostKey:string} + */ + private function buildConfig(): array + { + if ($this->configuration === null) { + $raw = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('ep_products'); + $raw = is_array($raw) ? $raw : []; + + $host = trim((string)($raw['productImportSftpHost'] ?? '')); + $user = trim((string)($raw['productImportSftpUser'] ?? '')); + $password = (string)($raw['productImportSftpPassword'] ?? ''); + $remotePath = trim((string)($raw['productImportSftpRemotePath'] ?? '/')); + $port = (int)($raw['productImportSftpPort'] ?? 22); + $timeout = (int)($raw['productImportSftpTimeout'] ?? 15); + $hostKey = trim((string)($raw['productImportSftpHostKey'] ?? '')); + + if ($host === '' || $user === '') { + throw new SftpImportException( + 'SFTP product import is not configured: host and username are required ' + . '(Admin Tools → Settings → Extension Configuration → ep_products).' + ); + } + if ($password === '') { + throw new SftpImportException('SFTP product import is not configured: password is required.'); + } + + $this->configuration = [ + 'host' => $host, + 'port' => $port > 0 ? $port : 22, + 'user' => $user, + 'password' => $password, + 'remotePath' => $remotePath === '' ? '/' : $remotePath, + 'timeout' => $timeout > 0 ? $timeout : 15, + 'hostKey' => $hostKey, + ]; + } + + return $this->configuration; + } +} diff --git a/packages/ep_products/Classes/Service/SnowreportImportService.php b/packages/ep_products/Classes/Service/SnowreportImportService.php new file mode 100644 index 0000000..26ce048 --- /dev/null +++ b/packages/ep_products/Classes/Service/SnowreportImportService.php @@ -0,0 +1,621 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Domain\Model\Snowreport; +use EP\EpProducts\Domain\Model\Webcam; +use EP\EpProducts\Domain\Repository\RegionRepository; +use EP\EpProducts\Domain\Repository\SnowreportRepository; +use EP\EpProducts\Domain\Repository\WebcamRepository; +use EP\EpProducts\Utility\SettingsUtility; +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; +use TYPO3\CMS\Core\Http\RequestFactory; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface; + +class SnowreportImportService implements SingletonInterface +{ + const XML_FILE_PATH = 'fileadmin/snowreport/snowreport.xml'; + const XML_FILE_URL = 'https://www.skiresort-service.com/xml-feed/'; + + /** + * Connection and read timeout for the feed download, in seconds. A healthy response to this + * endpoint arrives in well under a second, so the ceiling is kept low enough that an outage on + * the vendor side fails visibly instead of stalling the scheduler task or the backend request. + */ + const HTTP_CONNECT_TIMEOUT = 5; + const HTTP_TIMEOUT = 30; + + /** + * @var array + */ + protected $validSnowreportUids = []; + + /** + * @var array + */ + protected $validWebcamUids = []; + + /** + * Existing records keyed by vendor_uid, preloaded once per import to avoid a query per record. + * + * @var array + */ + protected $snowreportIndex = []; + + /** + * @var array + */ + protected $webcamIndex = []; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager + */ + protected $persistenceManager; + + /** + * @var \EP\EpProducts\Domain\Repository\SnowreportRepository + */ + protected $snowreportRepository; + + /** + * @var \EP\EpProducts\Domain\Repository\WebcamRepository + */ + protected $webcamRepository; + + /** + * @var \EP\EpProducts\Domain\Repository\RegionRepository + */ + protected $regionRepository; + + /** + * @param PersistenceManagerInterface $persistenceManager + * @param SnowreportRepository $snowreportRepository + * @param WebcamRepository $webcamRepository + * @param RegionRepository $regionRepository + */ + public function __construct( + PersistenceManagerInterface $persistenceManager, + SnowreportRepository $snowreportRepository, + WebcamRepository $webcamRepository, + RegionRepository $regionRepository + ) { + $this->persistenceManager = $persistenceManager; + $this->snowreportRepository = $snowreportRepository; + $this->webcamRepository = $webcamRepository; + $this->regionRepository = $regionRepository; + } + + const TYPE_INT = 'int'; + const TYPE_STRING = 'string'; + const TYPE_BOOL = 'bool'; + const TYPE_DATE = 'date'; + + /** + * Declared schema of the vendor feed: XML element name => target column and its type. + * + * XML carries no type information - every element is a string - so the type of each field is + * declared here rather than guessed from the value. Values are converted strictly according to + * this table; anything unexpected is logged and falls back to the type's empty value instead of + * silently changing type. + * + * @var array + */ + protected static $map = [ + 'region_id' => ['column' => 'vendor_uid', 'type' => self::TYPE_INT], + 'region_name' => ['column' => 'region_name', 'type' => self::TYPE_STRING], + 'region_offen' => ['column' => 'opened', 'type' => self::TYPE_BOOL], + 'gletschergebiet' => ['column' => 'glacier', 'type' => self::TYPE_BOOL], + 'schneehoehe_berg' => ['column' => 'snow_level_mountain', 'type' => self::TYPE_INT], + 'schneehoehe_tal' => ['column' => 'snow_level_valley', 'type' => self::TYPE_INT], + 'schneequalitaet' => ['column' => 'snow_quality', 'type' => self::TYPE_STRING], + 'letzter_schneefall' => ['column' => 'last_snowfall', 'type' => self::TYPE_DATE], + 'lifte_gesamt' => ['column' => 'lifts_total', 'type' => self::TYPE_INT], + 'offene_lifte' => ['column' => 'lifts_opened', 'type' => self::TYPE_INT], + 'letzte_aktualisierung' => ['column' => 'last_update', 'type' => self::TYPE_DATE], + 'hoehemax' => ['column' => 'height_max', 'type' => self::TYPE_INT], + 'hoehemin' => ['column' => 'height_min', 'type' => self::TYPE_INT], + 'datum_saisonstart' => ['column' => 'season_start', 'type' => self::TYPE_DATE], + 'datum_saisonende' => ['column' => 'season_end', 'type' => self::TYPE_DATE], + ]; + + /** + * Accepted boolean representations, lowercased. + * + * @var array + */ + protected static $booleanMap = [ + 'true' => true, + '1' => true, + 'ja' => true, + 'yes' => true, + 'false' => false, + '0' => false, + 'nein' => false, + 'no' => false, + ]; + + /** + * Distinct conversion problems encountered during the current import. + * + * @var array + */ + protected $conversionWarnings = []; + + /** + * @return int + */ + public function importSnowReportData() + { + $count = 0; + $file = GeneralUtility::getFileAbsFileName(self::XML_FILE_PATH); + + if (!is_file($file)) { + $this->writelog(sprintf('Snowreport XML import failed: file "%s" does not exist.', $file)); + + return 0; + } + + try { + $xmlData = $this->parseReportsXml((string) file_get_contents($file)); + } catch (\RuntimeException $e) { + $this->writelog(sprintf('Snowreport XML import failed: %s', $e->getMessage())); + + return 0; + } + + $this->resetImportState(); + $this->buildIndexes(); + + $snowReportStoragePid = $this->getStoragePid(); + + foreach ($xmlData->schneemeldung as $reportXml) + { + $vendorUid = (int) $reportXml->region_id; + $snowreport = $this->getSnowreport($vendorUid); + + $row = []; + foreach (static::$map as $key => $field) + { + if (!isset($reportXml->{$key})) { + $this->addConversionWarning(sprintf('Element <%s> missing in feed.', $key)); + $row[$field['column']] = $this->getEmptyValue($field['type']); + continue; + } + + $row[$field['column']] = $this->convertValue((string) $reportXml->{$key}, $field['type'], $key); + } + + $snowreport->setPid($snowReportStoragePid); + $snowreport->setVendorUid($vendorUid); + $snowreport->setRegionName($row['region_name']); + $snowreport->setOpened($row['opened']); + $snowreport->setGlacier($row['glacier']); + $snowreport->setSnowLevelMountain($row['snow_level_mountain']); + $snowreport->setSnowLevelValley($row['snow_level_valley']); + $snowreport->setSnowQuality($row['snow_quality']); + $snowreport->setLastSnowfall($row['last_snowfall']); + $snowreport->setLiftsTotal($row['lifts_total']); + $snowreport->setLiftsOpened($row['lifts_opened']); + $snowreport->setLastUpdate($row['last_update']); + $snowreport->setHeightMax($row['height_max']); + $snowreport->setHeightMin($row['height_min']); + $snowreport->setSeasonStart($row['season_start']); + $snowreport->setSeasonEnd($row['season_end']); + + if ($reportXml->webcam) { + $this->importWebcamData($reportXml->webcam, $snowreport); + } + + // New objects are already registered via add(); only managed ones need update(). + if (!$snowreport->_isNew()) { + $this->snowreportRepository->update($snowreport); + } + + $this->validSnowreportUids[] = $vendorUid; + + $count++; + } + + $this->persistenceManager->persistAll(); + + $this->cleanupData('tx_epproducts_domain_model_webcam', $this->validWebcamUids); + $this->cleanupData('tx_epproducts_domain_model_snowreport', $this->validSnowreportUids); + $this->updateWebcamCounters(); + + $this->logConversionWarnings(); + $this->writelog(sprintf('Snowreport XML import successful (%d records).', $count)); + + return $count; + } + + /** + * @param \SimpleXMLElement $xml + * @param Snowreport $snowreport + */ + public function importWebcamData(\SimpleXMLElement $xml, Snowreport $snowreport) + { + $snowReportStoragePid = $this->getStoragePid(); + foreach ($xml->children() as $item) + { + $include = $this->convertValue((string) $item->aktuell, self::TYPE_BOOL, 'aktuell'); + $available = $this->convertValue((string) $item->verf, self::TYPE_BOOL, 'verf'); + $vendorUid = (int) $item->uid; + + $webcam = isset($this->webcamIndex[$vendorUid]) ? $this->webcamIndex[$vendorUid] : null; + + // Skip cams that are neither current nor already known + if (null === $webcam && false === $include) { + continue; + } + + // Add new cam if not in db yet + if (null === $webcam) { + $webcam = new Webcam(); + $webcam->setVendorUid($vendorUid); + $webcam->setPid($snowReportStoragePid); + $this->webcamRepository->add($webcam); + $this->webcamIndex[$vendorUid] = $webcam; + $snowreport->addWebcam($webcam); + } + + $webcam->setUrl((string) $item->url); + $webcam->setName((string) $item->dt); + $webcam->setAvailable($available && $include); + + $this->validWebcamUids[] = $vendorUid; + } + } + + /** + * Flags records that are no longer contained in the feed as deleted. + * + * @param string $table + * @param array $validUids + * @return int Number of records flagged + */ + public function cleanupData($table, array $validUids) + { + /** @var ConnectionPool $connectionPool */ + $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); + $qb = $connectionPool->getQueryBuilderForTable($table); + // Hidden records are still managed by the import, only deleted ones are out of scope. + $qb->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)) + ; + $result = $qb + ->select('vendor_uid') + ->from($table) + ->execute() + ->fetchAllAssociative() + ; + + $currentUids = array_map(function ($row) { + return (int)$row['vendor_uid']; + }, $result); + + $obsoleteUids = array_values(array_diff($currentUids, $validUids)); + if (0 === count($obsoleteUids)) { + return 0; + } + + $qb = $connectionPool->getQueryBuilderForTable($table); + + return (int) $qb + ->update($table) + ->set('deleted', 1) + ->set('tstamp', isset($GLOBALS['EXEC_TIME']) ? $GLOBALS['EXEC_TIME'] : time()) + ->where( + $qb->expr()->in( + 'vendor_uid', + $qb->createNamedParameter($obsoleteUids, Connection::PARAM_INT_ARRAY) + ) + ) + ->execute() + ; + } + + /** + * Recalculates the inline child counter on the snowreport records. Webcams are flagged as deleted + * with plain SQL, which bypasses the counter Extbase maintains for the relation. + */ + protected function updateWebcamCounters() + { + $connection = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionForTable('tx_epproducts_domain_model_snowreport'); + + $connection->executeStatement( + 'UPDATE tx_epproducts_domain_model_snowreport s SET s.webcams = (' + . ' SELECT COUNT(*) FROM tx_epproducts_domain_model_webcam w' + . ' WHERE w.snowreport = s.uid AND w.deleted = 0' + . ')' + ); + } + + /** + * Downloads the feed and replaces the local file only if the response is a usable XML document. + * The existing file is left untouched on any failure. + * + * @return bool + */ + public function downloadReportsXml() + { + $path = GeneralUtility::getFileAbsFileName(self::XML_FILE_PATH); + + try { + $xmlContent = $this->fetchReportsXml(); + $this->parseReportsXml($xmlContent); + } catch (\Exception $e) { + $this->writelog(sprintf( + 'Snowreport XML download failed (%s). Keeping existing file.', + $e->getMessage() + )); + + return false; + } + + // Write to a temporary file first and move it into place, so the feed is either the old or the + // new document, never a partially written one. + $temporaryPath = $path . '.tmp'; + if (false === file_put_contents($temporaryPath, $xmlContent) || !rename($temporaryPath, $path)) { + @unlink($temporaryPath); + $this->writelog(sprintf('Snowreport XML could not be written to "%s".', $path)); + + return false; + } + + return true; + } + + /** + * @return string + * @throws \RuntimeException + */ + protected function fetchReportsXml() + { + /** @var RequestFactory $requestFactory */ + $requestFactory = GeneralUtility::makeInstance(RequestFactory::class); + + $response = $requestFactory->request(self::XML_FILE_URL, 'GET', [ + 'connect_timeout' => self::HTTP_CONNECT_TIMEOUT, + 'timeout' => self::HTTP_TIMEOUT, + 'headers' => [ + 'Accept' => 'application/xml, text/xml', + ], + ]); + + if (200 !== $response->getStatusCode()) { + throw new \RuntimeException( + sprintf('unexpected status code %d', $response->getStatusCode()), + 1758000001 + ); + } + + return (string) $response->getBody(); + } + + /** + * Parses the feed and rejects documents that are unusable. Without this an empty or truncated + * response would be imported as an empty feed, and the cleanup would flag every record as deleted. + * + * @param string $xmlContent + * @return \SimpleXMLElement + * @throws \RuntimeException + */ + protected function parseReportsXml($xmlContent) + { + if ('' === trim($xmlContent)) { + throw new \RuntimeException('response is empty', 1758000002); + } + + $useInternalErrors = libxml_use_internal_errors(true); + libxml_clear_errors(); + $xmlData = simplexml_load_string($xmlContent); + $errors = libxml_get_errors(); + libxml_clear_errors(); + libxml_use_internal_errors($useInternalErrors); + + if (false === $xmlData) { + $message = count($errors) > 0 ? trim($errors[0]->message) : 'unknown error'; + throw new \RuntimeException(sprintf('response is not valid XML (%s)', $message), 1758000003); + } + + if (0 === $xmlData->schneemeldung->count()) { + throw new \RuntimeException('response contains no elements', 1758000004); + } + + return $xmlData; + } + + /** + * Loads existing records once, keyed by vendor_uid. Replaces a repository lookup per feed record. + */ + protected function buildIndexes() + { + $this->snowreportIndex = []; + foreach ($this->snowreportRepository->findAll() as $snowreport) { + $this->snowreportIndex[(int) $snowreport->getVendorUid()] = $snowreport; + } + + $this->webcamIndex = []; + foreach ($this->webcamRepository->findAll() as $webcam) { + $this->webcamIndex[(int) $webcam->getVendorUid()] = $webcam; + } + } + + /** + * @param int $vendorUid + * @return Snowreport + */ + protected function getSnowreport($vendorUid) + { + if (isset($this->snowreportIndex[$vendorUid])) { + return $this->snowreportIndex[$vendorUid]; + } + + $snowreport = new Snowreport(); + $snowreport->setVendorUid($vendorUid); + $this->snowreportRepository->add($snowreport); + $this->snowreportIndex[$vendorUid] = $snowreport; + + return $snowreport; + } + + /** + * The service is a singleton, so per-import state must not survive into the next run. + */ + protected function resetImportState() + { + $this->validSnowreportUids = []; + $this->validWebcamUids = []; + $this->conversionWarnings = []; + } + + /** + * Converts a raw XML string to the declared type of the field. + * + * @param string $raw + * @param string $type + * @param string $key XML element name, for logging + * @return int|string|bool + */ + protected function convertValue($raw, $type, $key) + { + $raw = trim($raw); + + // The feed delivers empty elements for data that is not available yet (snow levels out of + // season, for example). That is expected, not an error. + if ($raw === '') { + return $this->getEmptyValue($type); + } + + switch ($type) { + case self::TYPE_STRING: + return $raw; + + case self::TYPE_INT: + if (!preg_match('/^-?\d+$/', $raw)) { + $this->addConversionWarning(sprintf('<%s> expected an integer, got "%s".', $key, $raw)); + + return 0; + } + + return (int) $raw; + + case self::TYPE_BOOL: + $normalized = strtolower($raw); + if (!array_key_exists($normalized, static::$booleanMap)) { + $this->addConversionWarning(sprintf('<%s> expected a boolean, got "%s".', $key, $raw)); + + return false; + } + + return static::$booleanMap[$normalized]; + + case self::TYPE_DATE: + if (!preg_match('/^\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}(:\d{2})?)?$/', $raw)) { + $this->addConversionWarning(sprintf('<%s> expected a date, got "%s".', $key, $raw)); + + return 0; + } + + $date = new \DateTime($raw); + + return $date->getTimestamp(); + } + + throw new \InvalidArgumentException(sprintf('Unknown field type "%s" declared for <%s>.', $type, $key), 1758000000); + } + + /** + * @param string $type + * @return int|string|bool + */ + protected function getEmptyValue($type) + { + if ($type === self::TYPE_STRING) { + return ''; + } + if ($type === self::TYPE_BOOL) { + return false; + } + + return 0; + } + + /** + * @param string $message + */ + protected function addConversionWarning($message) + { + // Keyed to keep one entry per distinct problem instead of one per record. + $this->conversionWarnings[$message] = true; + } + + /** + * Writes a single summary entry to the log if the feed contained unexpected values. + */ + protected function logConversionWarnings() + { + if (0 === count($this->conversionWarnings)) { + return; + } + + $messages = array_keys($this->conversionWarnings); + $this->writelog(sprintf( + 'Snowreport XML import: %d unexpected value(s) in feed: %s', + count($messages), + implode(' | ', array_slice($messages, 0, 10)) + )); + + $this->conversionWarnings = []; + } + + /** + * The scheduler task and the backend module provide a backend user, the CLI command does not. + * + * @param string $message + */ + protected function writelog($message) + { + if (isset($GLOBALS['BE_USER']) && is_object($GLOBALS['BE_USER'])) { + $GLOBALS['BE_USER']->writelog(4, 0, 0, 0, $message, []); + } + } + + public function getStoragePid() + { + $settingsUtility = new SettingsUtility(); + + return $settingsUtility->get('snowReportStoragePid'); + } +} diff --git a/packages/ep_products/Classes/Service/XmlProductReader.php b/packages/ep_products/Classes/Service/XmlProductReader.php new file mode 100644 index 0000000..1832bba --- /dev/null +++ b/packages/ep_products/Classes/Service/XmlProductReader.php @@ -0,0 +1,369 @@ +path = rtrim($path, '/'); + $this->logger = $logger ?? new NullLogger(); + $this->timezone = new \DateTimeZone(self::TIMEZONE); + $this->wallClock = new \DateTimeZone('UTC'); + } + + public function getPath(): string + { + return $this->path; + } + + /** + * The export's own timestamp from uebertragung.info, or null if unreadable. + */ + public function getSourceGeneratedAt(): ?\DateTimeImmutable + { + $file = $this->path . '/' . self::MARKER_FILE; + if (!is_file($file)) { + return null; + } + + $line = trim((string) strtok((string) file_get_contents($file), "\r\n")); + $date = \DateTimeImmutable::createFromFormat(self::MARKER_TS_FORMAT, $line, $this->timezone); + + return $date ?: null; + } + + /** + * @return \Generator + */ + public function getIterator(): \Generator + { + if ($this->pickups === null) { + $this->pickups = $this->readPickups(); + } + + $folder = opendir($this->path); + if ($folder === false) { + $this->logger->error('product xml directory not readable', ['path' => $this->path]); + return; + } + + try { + while (($fileName = readdir($folder)) !== false) { + if (strpos($fileName, self::PRODUCT_PREFIX) === 0) { + yield from $this->readProductFile($this->path . '/' . $fileName); + } + } + } finally { + closedir($folder); + } + } + + private function readProductFile(string $file): \Generator + { + libxml_use_internal_errors(true); + $xmlData = simplexml_load_string((string) file_get_contents($file)); + if ($xmlData === false) { + libxml_clear_errors(); + $this->logger->error('product xml invalid', ['file' => $file]); + return; + } + + $xmlProduct = $xmlData->reise[0]; + if ($xmlProduct === null) { + return; + } + + $product = [ + 'code' => (string) $xmlProduct['code'], + 'busProId' => (int) $xmlProduct['idbuspro'], + ]; + + foreach ($xmlProduct->termin as $xmlDate) { + $from = $this->parseDate((string) $xmlDate['termin']); + $to = $this->parseDate((string) $xmlDate['bis']); + if ($from === null || $to === null) { + $this->logger->warning('travel date skipped, dates unparseable', [ + 'code' => (string) $xmlDate['code'], + 'termin' => (string) $xmlDate['termin'], + 'bis' => (string) $xmlDate['bis'], + ]); + continue; + } + + $date = [ + 'code' => (string) $xmlDate['code'], + 'busProId' => (int) $xmlDate['idbuspro'], + 'from' => $from->format('Y-m-d'), + 'to' => $to->format('Y-m-d'), + 'type' => (string) $xmlDate['reiseart'], + ]; + + // Everything below is per travel date and shared by its hotels. + $selectionGroups = $this->readSelectionGroups($xmlDate); + $transportServices = $this->readServices($xmlDate, 'lei_befoerderung'); + $dateServices = $this->readServices($xmlDate, 'lei_sonstiges'); + $pickups = $this->readDatePickups($xmlDate, $from); + $guide = $this->readGuide($xmlDate); + + foreach ($xmlDate->hotel as $xmlHotel) { + [$pseudoPrice, $rooms] = $this->readRooms($xmlHotel); + + yield [ + 'product' => $product, + 'date' => $date, + 'hotel' => [ + 'code' => (string) $xmlHotel['code'], + 'busProId' => (int) $xmlHotel['idbuspro'], + ], + 'pseudoPrice' => $pseudoPrice, + 'selectionGroups' => $selectionGroups, + 'transportServices' => $transportServices, + 'dateServices' => $dateServices, + 'hotelServices' => $this->readServices($xmlHotel, 'lei_sonstiges'), + 'rooms' => $rooms, + 'pickups' => $pickups, + 'guide' => $guide, + ]; + } + } + } + + private function readSelectionGroups(\SimpleXMLElement $xmlDate): array + { + $groups = []; + foreach ($xmlDate->selektiongruppe as $xmlGroup) { + $selections = []; + foreach ($xmlGroup->selektion as $xmlSelection) { + $selections[] = [ + 'busProId' => (int) $xmlSelection['idbuspro'], + 'label' => (string) $xmlSelection['bezeichnung'], + ]; + } + $groups[] = [ + 'busProId' => (int) $xmlGroup['idbuspro'], + 'label' => (string) $xmlGroup['bezeichnung'], + 'selections' => $selections, + ]; + } + + return $groups; + } + + private function readServices(\SimpleXMLElement $xmlParent, string $list): array + { + $services = []; + foreach ($xmlParent->xpath($list . '/leistung') as $xmlService) { + $direction = trim((string) $xmlService->richtung); + $services[] = [ + 'busProId' => (int) $xmlService['idbuspro'], + 'subType' => (string) $xmlService['unterart'], + 'label' => $this->normalizeWhitespace((string) $xmlService->text), + 'price' => $this->parsePrice((string) $xmlService->preis), + 'status' => (string) $xmlService->status, + 'direction' => $direction === '' ? null : $direction, + ]; + } + + return $services; + } + + /** + * @return array{0: ?float, 1: array} + */ + private function readRooms(\SimpleXMLElement $xmlHotel): array + { + $pseudoPrice = null; + $rooms = []; + foreach ($xmlHotel->xpath('zimmer/preis') as $xmlRoom) { + $code = trim((string) $xmlRoom['zimmercode']); + if ($code === self::CODE_PSEUDOPRICE) { + $pseudoPrice = $this->parsePrice((string) $xmlRoom['preis']); + continue; + } + + $rooms[] = [ + 'busProId' => (int) $xmlRoom['idbuspro_zimmer'], + 'code' => $code, + 'label' => (string) $xmlRoom['zimmertext'], + 'category' => (string) $xmlRoom['kat'], + 'minPax' => $this->parseInt((string) $xmlRoom['MinPax']), + 'maxPax' => $this->parseInt((string) $xmlRoom['MaxPax']), + 'nights' => $this->parseInt((string) $xmlRoom['naechte']), + 'price' => $this->parsePrice((string) $xmlRoom['preis']), + 'available' => $this->parseInt((string) $xmlRoom['verfuegbar']), + 'status' => (string) $xmlRoom['status'], + 'boardCode' => (string) $xmlRoom['vpcode'], + 'boardBusProId' => $this->parseInt((string) $xmlRoom['idbuspro_vp']), + ]; + } + + return [$pseudoPrice, $rooms]; + } + + /** + * A bare HH:mm takes the travel date's start, and is rolled onto the next + * day when it would otherwise fall before the latest full date-time listed + * before it: a coach does not board backwards. The result is the local + * date and time as BusPro states it, without an offset. + */ + private function readDatePickups(\SimpleXMLElement $xmlDate, \DateTimeImmutable $from): array + { + $pickups = []; + $latest = null; + foreach ($xmlDate->xpath('zustiege/zustieg') as $xmlPickup) { + $busProId = (int) $xmlPickup['idbuspro']; + $time = trim((string) $xmlPickup['zeit']); + + $datetime = null; + if ($time !== '' && strpos($time, ' ') !== false) { + $datetime = \DateTimeImmutable::createFromFormat('!d.m.Y H:i:s', $time, $this->wallClock) ?: null; + if ($datetime !== null && ($latest === null || $datetime > $latest)) { + $latest = $datetime; + } + } elseif ($time !== '') { + $datetime = \DateTimeImmutable::createFromFormat('!Y-m-d H:i', $from->format('Y-m-d') . ' ' . $time, $this->wallClock) ?: null; + if ($datetime !== null && $latest !== null && $datetime < $latest) { + $datetime = $datetime->modify('+1 day'); + } + } + + $masterData = $this->pickups[$busProId] ?? null; + $pickups[] = [ + 'busProId' => $busProId, + 'code' => $masterData['code'] ?? null, + 'city' => $masterData['city'] ?? null, + 'street' => $masterData['street'] ?? null, + 'price' => $this->parsePrice((string) $xmlPickup['preis']), + 'datetime' => $datetime !== null ? $datetime->format(self::LOCAL_DATETIME_FORMAT) : null, + ]; + } + + return $pickups; + } + + private function readGuide(\SimpleXMLElement $xmlDate): ?array + { + foreach ($xmlDate->xpath('lei_befoerderung/leistung[@unterart="BUS"]') as $xmlService) { + if ((string) $xmlService->richtung === 'RUECK') { + continue; + } + if (!isset($xmlService->zustiegsplanung->reiseleiter)) { + continue; + } + + return [ + 'name' => (string) $xmlService->zustiegsplanung->reiseleiter->name, + 'phone' => (string) $xmlService->zustiegsplanung->reiseleiter->telefon, + ]; + } + + return null; + } + + /** + * @return array + */ + private function readPickups(): array + { + $file = $this->path . '/' . self::PICKUPS_FILE; + libxml_use_internal_errors(true); + if (!is_file($file) || !$xmlData = simplexml_load_string((string) file_get_contents($file))) { + libxml_clear_errors(); + $this->logger->warning('pickup xml missing or invalid', ['file' => $file]); + return []; + } + + $pickups = []; + foreach ($xmlData->zustieg as $xmlPickup) { + $pickups[(int) $xmlPickup['idbuspro']] = [ + 'code' => (string) $xmlPickup['code'], + 'city' => $this->normalizeWhitespace((string) $xmlPickup->ort), + 'street' => $this->normalizeWhitespace((string) $xmlPickup->strasse), + ]; + } + + return $pickups; + } + + private function normalizeWhitespace(string $value): string + { + return trim((string) preg_replace('/\s+/u', ' ', $value)); + } + + private function parseDate(string $value): ?\DateTimeImmutable + { + $date = \DateTimeImmutable::createFromFormat('!d.m.Y', trim($value), $this->timezone); + + return $date ?: null; + } + + /** + * German format with an optional thousands separator: "1.234,50" is 1234.5. + */ + private function parsePrice(string $value): ?float + { + $value = str_replace(['.', ','], ['', '.'], trim($value)); + + return is_numeric($value) ? (float) $value : null; + } + + private function parseInt(string $value): ?int + { + $value = trim($value); + + return is_numeric($value) ? (int) $value : null; + } +} diff --git a/packages/ep_products/Classes/Task/ImportContingentsTask.php b/packages/ep_products/Classes/Task/ImportContingentsTask.php new file mode 100644 index 0000000..b9aeaa2 --- /dev/null +++ b/packages/ep_products/Classes/Task/ImportContingentsTask.php @@ -0,0 +1,49 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\ContingentImportService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Scheduler\Task\AbstractTask; + +class ImportContingentsTask extends AbstractTask +{ + public function execute() + { + /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + /** @var \EP\EpProducts\Service\ContingentImportService $importService */ + $importService = $objectManager->get(ContingentImportService::class); + if ($importService->isImportRequired()) { + $importService->import(); + } + + return true; + } +} diff --git a/packages/ep_products/Classes/Task/ImportDatesTask.php b/packages/ep_products/Classes/Task/ImportDatesTask.php new file mode 100644 index 0000000..d1edeb9 --- /dev/null +++ b/packages/ep_products/Classes/Task/ImportDatesTask.php @@ -0,0 +1,74 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\DateImportService; +use EP\EpProducts\Service\ProductImportSourceFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManager; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; +use TYPO3\CMS\Extbase\Service\CacheService; +use TYPO3\CMS\Scheduler\Task\AbstractTask; + +class ImportDatesTask extends AbstractTask +{ + public function execute(): bool + { + $importService = GeneralUtility::makeInstance(DateImportService::class); + $importSource = GeneralUtility::makeInstance(ProductImportSourceFactory::class)->get(); + $cacheService = GeneralUtility::makeInstance(CacheService::class); + + // Acquire the export only when it is newer than the one we last imported + // (SFTP mode: download to a staging dir; local mode: fileadmin/xmlexport; + // API mode: walk the MyE&P export pages). An acquisition failure is left + // to propagate so the scheduler marks the task failed. + $import = $importSource->acquire(); + + if ($import !== null) { + $importService->import($import->getRows()); + // Advance the "last imported" bookkeeping only after a successful + // import, so a crashed import retries on the next run. + $importSource->markImported($import); + } + + $settings = $this->getSettings(); + $travelInfoPageUid = $settings['travelInfoPageUid']; + $cacheService->clearPageCache($travelInfoPageUid); + + return true; + } + + protected function getSettings(): array + { + $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); + $settings = $configurationManager + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); + + return $settings['plugin.']['tx_epproducts.']['settings.']; + } +} diff --git a/packages/ep_products/Classes/Task/ImportSnowreportTask.php b/packages/ep_products/Classes/Task/ImportSnowreportTask.php new file mode 100644 index 0000000..ea8dcda --- /dev/null +++ b/packages/ep_products/Classes/Task/ImportSnowreportTask.php @@ -0,0 +1,46 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use EP\EpProducts\Service\SnowreportImportService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Scheduler\Task\AbstractTask; + +class ImportSnowreportTask extends AbstractTask +{ + public function execute() + { + /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $snowreportService = $objectManager->get(SnowreportImportService::class); + $snowreportService->downloadReportsXml(); + $snowreportService->importSnowReportData(); + return true; + } +} diff --git a/packages/ep_products/Classes/Traits/DbConnectionTrait.php b/packages/ep_products/Classes/Traits/DbConnectionTrait.php new file mode 100644 index 0000000..27aab94 --- /dev/null +++ b/packages/ep_products/Classes/Traits/DbConnectionTrait.php @@ -0,0 +1,49 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +trait DbConnectionTrait +{ + + /** + * @param string $connectionName + * @return \TYPO3\CMS\Core\Database\Connection + * @throws \Doctrine\DBAL\DBALException + */ + protected function getDbConnection($connectionName = 'Default') + { + /** @var ConnectionPool $connectionPool */ + $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); + + return $connectionPool->getConnectionByName($connectionName); + } + +} diff --git a/packages/ep_products/Classes/Traits/RequestArgumentTypeConversionTrait.php b/packages/ep_products/Classes/Traits/RequestArgumentTypeConversionTrait.php new file mode 100644 index 0000000..f7f72ba --- /dev/null +++ b/packages/ep_products/Classes/Traits/RequestArgumentTypeConversionTrait.php @@ -0,0 +1,53 @@ +get(SearchResultService::class); + + if (!$this->request->hasArgument('searchParams')) { + $searchParams = $searchResultService->getNormalizedSearchParams(); + } else { + $requestArgument = $this->request->getArgument('searchParams'); + $searchParamsFromRequest = is_array($requestArgument) ? + $requestArgument : json_decode($requestArgument, true, 512, JSON_THROW_ON_ERROR); + $searchParams = $searchResultService->getNormalizedSearchParams($searchParamsFromRequest); + } + + $this->request->setArgument('searchParams', $searchParams); + } + + public function processFilterSettingsArgument(array $forcedConceptUids = []): void + { + /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + + /** @var \EP\EpProducts\Service\FilterService $filterService */ + $filterService = $objectManager->get(FilterService::class); + + if (!$this->request->hasArgument('filterSettings')) { + $filterSettings = $filterService->getNormalizedFilterSettings([], $forcedConceptUids); + } else { + $requestArgument = $this->request->getArgument('filterSettings'); + $filterSettingsFromRequest = is_array($requestArgument) ? + $requestArgument : json_decode($requestArgument, true, 512, JSON_THROW_ON_ERROR); + $filterSettings = $this->filterService->getNormalizedFilterSettings($filterSettingsFromRequest, $forcedConceptUids); + } + + $this->request->setArgument('filterSettings', $filterSettings); + } + +} diff --git a/packages/ep_products/Classes/Updates/TeamSlugUpdater.php b/packages/ep_products/Classes/Updates/TeamSlugUpdater.php new file mode 100644 index 0000000..5f1d8e0 --- /dev/null +++ b/packages/ep_products/Classes/Updates/TeamSlugUpdater.php @@ -0,0 +1,121 @@ +countRequiredUpdates(); + + return (bool)$elementCount; + } + + /** + * @return bool + */ + public function executeUpdate(): bool + { + $queryBuilder = $this->getQueryBuilder(); + + $statement = $queryBuilder->select('uid', 'name') + ->from('tx_epproducts_domain_model_teammember') + ->where( + $queryBuilder->expr()->orX( + $queryBuilder->expr()->eq( + 'path_segment', + $queryBuilder->createNamedParameter('') + ), + $queryBuilder->expr()->isNull('path_segment') + ) + ) + ->execute(); + while ($record = $statement->fetch()) { + $slug = Transliterator::urlize($record['name']); + $queryBuilder = $this->getQueryBuilder(); + $queryBuilder->update('tx_epproducts_domain_model_teammember') + ->where( + $queryBuilder->expr()->eq( + 'uid', + $queryBuilder->createNamedParameter($record['uid'], \PDO::PARAM_INT) + ) + ) + ->set('path_segment', $slug); + $queryBuilder->execute(); + } + + return true; + } + + protected function countRequiredUpdates(): int + { + $queryBuilder = $this->getQueryBuilder(); + + return $queryBuilder->count('uid') + ->from('tx_epproducts_domain_model_teammember') + ->where( + $queryBuilder->expr()->orX( + $queryBuilder->expr()->eq( + 'path_segment', + $queryBuilder->createNamedParameter('') + ), + $queryBuilder->expr()->isNull('path_segment') + ) + ) + ->execute()->fetchColumn(); + } + + protected function getQueryBuilder(): QueryBuilder + { + /** @var QueryBuilder $queryBuilder */ + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_epproducts_domain_model_teammember'); + $queryBuilder->getRestrictions()->removeAll(); + + return $queryBuilder; + } + + /** + * @return string[] + */ + public function getPrerequisites(): array + { + return [ + DatabaseUpdatedPrerequisite::class, + ]; + } +} diff --git a/packages/ep_products/Classes/UserFunc/TcaProcFunc.php b/packages/ep_products/Classes/UserFunc/TcaProcFunc.php new file mode 100644 index 0000000..2932e6f --- /dev/null +++ b/packages/ep_products/Classes/UserFunc/TcaProcFunc.php @@ -0,0 +1,48 @@ +getQueryBuilderForTable('tx_epproducts_domain_model_date'); + + $result = $qb + ->select('date_start', 'date_end', 'bus_pro_id') + ->from('tx_epproducts_domain_model_date') + ->where($qb->expr()->eq('product', $productUid)) + ->orderBy('date_start') + ->execute() + ->fetchAll() + ; + + foreach ($result as $row) { + $dateStart = new \DateTime($row['date_start']); + $dateEnd = new \DateTime($row['date_end']); + + $items[] = [ + sprintf('%s - %s', $dateStart->format('d.m.Y'), $dateEnd->format('d.m.Y')), + $row['bus_pro_id'], + ]; + } + + $config['items'] = $items; + + return $config; + } +} \ No newline at end of file diff --git a/packages/ep_products/Classes/Utility/BookingUrlUtility.php b/packages/ep_products/Classes/Utility/BookingUrlUtility.php new file mode 100644 index 0000000..24f4892 --- /dev/null +++ b/packages/ep_products/Classes/Utility/BookingUrlUtility.php @@ -0,0 +1,40 @@ +setDefined(['paCode', 'returnPageUid']); + $resolver->setRequired(['dateId', 'hotelId', 'myEpUrl']); + + $resolvedOptions = $resolver->resolve($options); + + $query = [ + 'date_id' => $resolvedOptions['dateId'], + 'hotel_id' => $resolvedOptions['hotelId'], + ]; + + if (null !== $resolvedOptions['paCode']) { + $query['agency'] = $resolvedOptions['paCode']; + } + + if (null !== $resolvedOptions['returnPageUid'] && 0 < (int) $resolvedOptions['returnPageUid']) { + $siteFinder = GeneralUtility::makeInstance(SiteFinder::class); + $site = $siteFinder->getSiteByPageId((int) $resolvedOptions['returnPageUid']); + + $query['r'] = (string) $site->getRouter()->generateUri((int) $resolvedOptions['returnPageUid'], [], '', RouterInterface::ABSOLUTE_URL); + } + + $myEpUrl = rtrim($resolvedOptions['myEpUrl'], '/'); + + return $myEpUrl . '/bookings/create?' . http_build_query($query); + } +} diff --git a/packages/ep_products/Classes/Utility/DateUtility.php b/packages/ep_products/Classes/Utility/DateUtility.php new file mode 100644 index 0000000..033aaa4 --- /dev/null +++ b/packages/ep_products/Classes/Utility/DateUtility.php @@ -0,0 +1,82 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +class DateUtility +{ + + public static function formatDateRange + ( + ?string $dateFromString = '', + ?string $dateToString = '', + bool $includeLabel = true, + string $format = 'd.m.Y' + ): string { + if (empty($dateFromString) && empty($dateToString)) { + return ''; + } + $dateFrom = new \DateTime($dateFromString); + $dateTo = new \DateTime($dateToString); + $label = $includeLabel ? 'im Zeitraum ' : ''; + if ($dateFromString !== '' && $dateToString === '') { + return $label . 'ab ' . $dateFrom->format($format); + } + if ($dateFromString === '' && $dateToString !== '') { + return $label . '-' . $dateTo->format($format); + } + return $label . $dateFrom->format($format) . '-' . $dateTo->format($format); + } + + public static function getDateRange($fromYear = null, $fromMonth = null, int $months = 6): array + { + $now = new \DateTime('now'); + $currentMonth = (int) $now->format('m'); + $currentYear = (int) $now->format('Y'); + + if ($fromYear === null || (int) $fromYear < $currentYear) { + $fromYear = $currentYear; + } + + if ($fromMonth === null || ((int) $fromYear === $currentYear && (int) $fromMonth < $currentMonth)) { + $fromMonth = $currentMonth; + } + + $begin = new \DateTimeImmutable(); + $begin = $begin->setDate((int) $fromYear, (int) $fromMonth, 1)->setTime(0, 0, 0); + $end = $begin->add(new \DateInterval('P' . $months . 'M')); + $period = new Period($begin, $end); + + return [ + 'period' => $period, + 'dateFrom' => $period->getStartDate()->format('Y-m-d'), + 'dateTo' => $period->getEndDate()->sub(new \DateInterval('P1D'))->format('Y-m-d'), + ]; + } +} diff --git a/packages/ep_products/Classes/Utility/HotelIconUtility.php b/packages/ep_products/Classes/Utility/HotelIconUtility.php new file mode 100644 index 0000000..21cb870 --- /dev/null +++ b/packages/ep_products/Classes/Utility/HotelIconUtility.php @@ -0,0 +1,83 @@ + + */ + public static function getIcons(array $hotel): array + { + $icons = []; + + if (($hotel['beds_count'] ?? 0) > 0) { + $icons[] = [ + 'icon' => 'bed', + 'label' => sprintf('%d Betten', $hotel['beds_count']), + ]; + } + if (($hotel['parking_count'] ?? 0) > 0) { + $icons[] = [ + 'icon' => 'parking', + 'label' => sprintf('%d Parkplätze', $hotel['parking_count']), + ]; + } + if ($hotel['altitude'] ?? null) { + $icons[] = [ + 'icon' => 'mountain', + 'label' => $hotel['altitude'], + ]; + } + if ($hotel['distance_bus'] ?? null) { + $icons[] = [ + 'icon' => 'bus', + 'label' => $hotel['distance_bus'], + ]; + } + if ($hotel['distance_lift'] ?? null) { + $icons[] = [ + 'icon' => 'lift', + 'label' => $hotel['distance_lift'], + ]; + } + if ($hotel['shower'] ?? null) { + $icons[] = [ + 'icon' => 'shower', + 'label' => $hotel['shower'], + ]; + } + if ($hotel['partyroom'] ?? false) { + $icons[] = [ + 'icon' => 'party', + 'label' => 'Partyzimmer', + ]; + } + if ($hotel['gamesroom'] ?? false) { + $icons[] = [ + 'icon' => 'dice', + 'label' => 'Spielezimmer', + ]; + } + if ($hotel['sauna'] ?? false) { + $icons[] = [ + 'icon' => 'sauna', + 'label' => 'Sauna', + ]; + } + if ($hotel['wifi'] ?? false) { + $icons[] = [ + 'icon' => 'wifi', + 'label' => 'WiFi', + ]; + } + + return $icons; + } +} diff --git a/packages/ep_products/Classes/Utility/SerializationUtility.php b/packages/ep_products/Classes/Utility/SerializationUtility.php new file mode 100644 index 0000000..02a0ca0 --- /dev/null +++ b/packages/ep_products/Classes/Utility/SerializationUtility.php @@ -0,0 +1,108 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Extbase\Persistence\ObjectStorage; +use TYPO3\CMS\Extbase\Service\ImageService; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +class SerializationUtility +{ + /** + * @var ImageService + */ + protected static $imageService; + + /** + * @var ContentObjectRenderer + */ + protected static $contentObject; + + /** + * @param $text + * @return string + */ + public static function sanitizeText($text) + { + $text = str_replace(' ', ' ', $text); + + return trim($text); + } + + /** + * @param ObjectStorage $images + * @param array $processingInstructions + * @return array + */ + public static function preprocessImages(ObjectStorage $images, array $processingInstructions = null) + { + $data = []; + foreach ($images as $file) + { + $image = $file->getOriginalResource(); + if (TYPO3_MODE === 'BE') { + $hostname = 'www.ep-reisen.de'; + } else { + $hostname = GeneralUtility::getIndpEnv('HTTP_HOST'); + } + if (!\is_array($processingInstructions)) { + $imageUrl = $image->getOriginalFile()->getPublicUrl(); + } else { + $imageService = static::getImageService(); + $cropString = $image->getProperty('crop'); + $cropVariantCollection = CropVariantCollection::create($cropString); + $cropVariant = $processingInstructions['cropVariant'] ?: 'default'; + $cropArea = $cropVariantCollection->getCropArea($cropVariant); + $processingInstructions['crop'] = $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image); + $processedImage = $imageService->applyProcessingInstructions($image, $processingInstructions); + $imageUrl = $processedImage->getPublicUrl(); + } + $data[] = sprintf('https://%s/%s', $hostname, $imageUrl); + } + + return $data; + } + + /** + * @return ImageService + */ + protected static function getImageService() + { + if (static::$imageService === null) { + /** @var ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + static::$imageService = $objectManager->get(ImageService::class); + } + + return static::$imageService; + } + +} diff --git a/packages/ep_products/Classes/Utility/SettingsUtility.php b/packages/ep_products/Classes/Utility/SettingsUtility.php new file mode 100644 index 0000000..4e6fecd --- /dev/null +++ b/packages/ep_products/Classes/Utility/SettingsUtility.php @@ -0,0 +1,65 @@ +, dreipunktnull + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManager; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; +use TYPO3\CMS\Extbase\Object\ObjectManager; + +class SettingsUtility +{ + /** + * @var array + */ + protected $settings = []; + + public function __construct() + { + /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */ + $objectManager = GeneralUtility::makeInstance(ObjectManager::class); + $configurationManager = $objectManager->get(ConfigurationManager::class); + $settings = GeneralUtility::removeDotsFromTS( + $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT) + ); + $this->settings = $settings['plugin']['tx_epproducts']['settings']; + } + + /** + * @param string $key + * @return string|array + */ + public function get($key = null) + { + if ($key !== null && array_key_exists($key, $this->settings)) { + return $this->settings[$key]; + } + + return $this->settings; + } +} diff --git a/packages/ep_products/Classes/Utility/TypeConversionUtility.php b/packages/ep_products/Classes/Utility/TypeConversionUtility.php new file mode 100644 index 0000000..2168d79 --- /dev/null +++ b/packages/ep_products/Classes/Utility/TypeConversionUtility.php @@ -0,0 +1,32 @@ + [ + 'class' => \EP\EpProducts\Command\ExportCommandController::class, + 'scheduleable' => false, + ], + 'ep:groups-swiss' => [ + 'class' => \EP\EpProducts\Command\GroupsSwissCommand::class, + ], + 'ep:contingent-import' => [ + 'class' => \EP\EpProducts\Command\ContingentCommand::class, + 'scheduleable' => false, + ], +]; diff --git a/packages/ep_products/Configuration/FlexForms/flexform_city_detail.xml b/packages/ep_products/Configuration/FlexForms/flexform_city_detail.xml new file mode 100644 index 0000000..0541cd5 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_city_detail.xml @@ -0,0 +1,40 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_city + AND tx_epproducts_domain_model_city.hidden = 0 AND tx_epproducts_domain_model_city.deleted = 0 ORDER BY tx_epproducts_domain_model_city.name + 1 + 1 + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_city_teaser.xml b/packages/ep_products/Configuration/FlexForms/flexform_city_teaser.xml new file mode 100644 index 0000000..a98c0d9 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_city_teaser.xml @@ -0,0 +1,79 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_country + ORDER BY name + + + + 0 + + + 0 + 1 + + + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_region + ORDER BY name + + + + 0 + + + 0 + 1 + + + + + + 0 + + + select + selectSingle + + + Winter + w + + + Sommer + s + + + 1 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_concept_detail.xml b/packages/ep_products/Configuration/FlexForms/flexform_concept_detail.xml new file mode 100644 index 0000000..cf5461c --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_concept_detail.xml @@ -0,0 +1,69 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_concept + AND tx_epproducts_domain_model_concept.hidden = 0 AND tx_epproducts_domain_model_concept.deleted = 0 ORDER BY tx_epproducts_domain_model_concept.name + + + Alle + 0 + + + 1 + 1 + + + + + + + + select + selectSingleBox + tx_epproducts_domain_model_concept + AND tx_epproducts_domain_model_concept.hidden = 0 AND tx_epproducts_domain_model_concept.deleted = 0 ORDER BY tx_epproducts_domain_model_concept.name + 5 + 0 + 10 + + + + + + 0 + + + check + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_country.xml b/packages/ep_products/Configuration/FlexForms/flexform_country.xml new file mode 100644 index 0000000..4b21262 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_country.xml @@ -0,0 +1,46 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_country + AND tx_epproducts_domain_model_country.hidden = 0 AND tx_epproducts_domain_model_country.deleted = 0 ORDER BY tx_epproducts_domain_model_country.name + + + + 0 + + + 0 + 1 + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_faq.xml b/packages/ep_products/Configuration/FlexForms/flexform_faq.xml new file mode 100644 index 0000000..cb9d809 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_faq.xml @@ -0,0 +1,32 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectMultipleSideBySide + tx_epproducts_domain_model_faq + AND tx_epproducts_domain_model_faq.hidden = 0 AND tx_epproducts_domain_model_faq.deleted = 0 ORDER BY tx_epproducts_domain_model_faq.label + 1 + 50 + 10 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail.xml b/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail.xml new file mode 100644 index 0000000..7dd6304 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail.xml @@ -0,0 +1,46 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_hotel + AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name + + + Alle + 0 + + + 0 + 1 + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail_event.xml b/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail_event.xml new file mode 100644 index 0000000..5a6878f --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_hotel_detail_event.xml @@ -0,0 +1,51 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_product + AND tx_epproducts_domain_model_product.hidden = 0 AND tx_epproducts_domain_model_product.deleted = 0 ORDER BY tx_epproducts_domain_model_product.name_internal + 1 + 1 + + + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_hotel + AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name + + + Alle + 0 + + + 1 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser.xml b/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser.xml new file mode 100644 index 0000000..62e436d --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser.xml @@ -0,0 +1,217 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + + + vierspaltig + 4 + + + dreispaltig + 3 + + + zweispaltig + 2 + + + 1 + 1 + + + + + + 0 + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_country + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + 0 + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_region + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + 0 + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_city + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_concept + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + reload + + select + selectSingle + tx_epproducts_domain_model_product + ORDER BY name_internal + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + FIELD:settings.productUid:REQ:TRUE + + select + selectSingle + + + Alle + 0 + + + Standard + 1 + + + Basic + 5 + + + Komfort + 2 + + + Deluxe + 3 + + + Superdeluxe + 4 + + + 0 + 1 + + + + + + + + select + selectCheckBox + + + Ferienwohnung + 1 + + + Sportclub + 2 + + + Apartment + 3 + + + Gästerhaus + 4 + + + Pension + 5 + + + 0 + 10 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser_date_independent.xml b/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser_date_independent.xml new file mode 100644 index 0000000..47dfae6 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_hotel_teaser_date_independent.xml @@ -0,0 +1,176 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + + + vierspaltig + 4 + + + dreispaltig + 3 + + + zweispaltig + 2 + + + 1 + 1 + + + + + + 0 + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_country + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + 0 + + FIELD:settings.productUid:REQ:FALSE + + select + selectSingle + tx_epproducts_domain_model_region + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + reload + + select + selectSingle + tx_epproducts_domain_model_product + ORDER BY name_internal + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + FIELD:settings.productUid:REQ:TRUE + + select + selectSingle + + + Alle + 0 + + + Standard + 1 + + + Basic + 5 + + + Komfort + 2 + + + Deluxe + 3 + + + Superdeluxe + 4 + + + 0 + 1 + + + + + + + + select + selectCheckBox + + + Ferienwohnung + 1 + + + Sportclub + 2 + + + Apartment + 3 + + + Gästerhaus + 4 + + + Pension + 5 + + + 0 + 10 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_maps.xml b/packages/ep_products/Configuration/FlexForms/flexform_maps.xml new file mode 100644 index 0000000..c973c2e --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_maps.xml @@ -0,0 +1,47 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectMultipleSideBySide + tx_epproducts_domain_model_hotel + AND tx_epproducts_domain_model_hotel.latitude != "" AND tx_epproducts_domain_model_hotel.longitude != "" ORDER BY name + 0 + 50 + 10 + + + + + + 0 + + + select + selectMultipleSideBySide + tx_epproducts_domain_model_product + AND tx_epproducts_domain_model_product.detail_page != 0 ORDER BY name_internal + 0 + 50 + 10 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_pricetable.xml b/packages/ep_products/Configuration/FlexForms/flexform_pricetable.xml new file mode 100644 index 0000000..af6b5ae --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_pricetable.xml @@ -0,0 +1,65 @@ + + + 1 + + + + + + Konfiguration + + array + + + + + reload + + select + selectSingle + Pricetable->event + + + Preistabelle Eventreise + Pricetable->event + + + + + + + + + + check + 0 + + + + + + + + input + trim + + + + + + + + select + selectSingle + tx_epproducts_domain_model_product + AND tx_epproducts_domain_model_product.hidden = 0 AND tx_epproducts_domain_model_product.deleted = 0 ORDER BY tx_epproducts_domain_model_product.name_internal + 1 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_product_detail.xml b/packages/ep_products/Configuration/FlexForms/flexform_product_detail.xml new file mode 100644 index 0000000..4b1e2bf --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_product_detail.xml @@ -0,0 +1,75 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_product + AND tx_epproducts_domain_model_product.hidden = 0 AND tx_epproducts_domain_model_product.deleted = 0 ORDER BY tx_epproducts_domain_model_product.name_internal + + + Alle + 0 + + + 0 + 1 + + + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_hotel + AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name + + + Alle + 0 + + + 0 + 1 + + + + + + 0 + + + check + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_product_pricetable.xml b/packages/ep_products/Configuration/FlexForms/flexform_product_pricetable.xml new file mode 100644 index 0000000..5a6878f --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_product_pricetable.xml @@ -0,0 +1,51 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_product + AND tx_epproducts_domain_model_product.hidden = 0 AND tx_epproducts_domain_model_product.deleted = 0 ORDER BY tx_epproducts_domain_model_product.name_internal + 1 + 1 + + + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_hotel + AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name + + + Alle + 0 + + + 1 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_product_teaser.xml b/packages/ep_products/Configuration/FlexForms/flexform_product_teaser.xml new file mode 100644 index 0000000..9b9bfcb --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_product_teaser.xml @@ -0,0 +1,252 @@ + + + 1 + + + + + + Konfiguration + + array + + + + + reload + + select + selectSingle + Product->teasergroup + + + Teasergruppe + Product->teasergroup + + + Einzelteaser + Product->teaser + + + + + + + + + teasergroup]]> + + select + selectSingle + tx_epproducts_domain_model_country + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + teasergroup]]> + + select + selectSingle + tx_epproducts_domain_model_region + ORDER BY name + + + Keine Zuordnung + 0 + + + 0 + 1 + + + + + + + teasergroup]]> + + select + selectSingleBox + tx_epproducts_domain_model_concept + ORDER BY name + + + Keine Zuordnung + 0 + + + 5 + 0 + 10 + + + + + + + + select + selectSingle + tx_epproducts_domain_model_hotel + ORDER BY name + + + Alle + 0 + + + 0 + 1 + + + + + + + teasergroup]]> + + check + + + + + + + teasergroup]]> + + input + 10 + date + + + + + + + teasergroup]]> + + input + 10 + date + + + + + + + teaser]]> + + select + selectSingle + tx_epproducts_domain_model_product + ORDER BY name_internal + + + Alle + 0 + + + 0 + 1 + + + + + + + teasergroup]]> + + select + selectSingle + + + Spalten + cols + + + Reihen + rows + + + + + + + + + teasergroup]]> + + select + selectCheckBox + + + Ferienwohnung + 1 + + + Sportclub + 2 + + + Apartment + 3 + + + Gästehaus + 4 + + + Pension + 5 + + + 0 + 10 + + + + + + 0 + + + check + + + + + + + teasergroup]]> + + select + selectSingle + tx_epproducts_domain_model_infobox + ORDER BY title + + + Default + 0 + + + 0 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_region_detail.xml b/packages/ep_products/Configuration/FlexForms/flexform_region_detail.xml new file mode 100644 index 0000000..f0f3602 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_region_detail.xml @@ -0,0 +1,54 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_region + AND tx_epproducts_domain_model_region.hidden = 0 AND tx_epproducts_domain_model_region.deleted = 0 ORDER BY tx_epproducts_domain_model_region.name + 1 + 1 + + + + + + 0 + + + select + selectMultipleSideBySide + tx_epproducts_domain_model_region + AND tx_epproducts_domain_model_region.hidden = 0 AND tx_epproducts_domain_model_region.deleted = 0 ORDER BY tx_epproducts_domain_model_region.name + 0 + 5 + + + + + + 0 + + + check + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_region_teaser.xml b/packages/ep_products/Configuration/FlexForms/flexform_region_teaser.xml new file mode 100644 index 0000000..69dd942 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_region_teaser.xml @@ -0,0 +1,53 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectSingle + tx_epproducts_domain_model_country + ORDER BY name + 1 + 1 + + + + + + 0 + + + select + selectSingle + + + Winter + w + + + Sommer + s + + + 1 + 1 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_snowreport.xml b/packages/ep_products/Configuration/FlexForms/flexform_snowreport.xml new file mode 100644 index 0000000..6e865c8 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_snowreport.xml @@ -0,0 +1,32 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + select + selectMultipleSideBySide + tx_epproducts_domain_model_region + AND tx_epproducts_domain_model_region.snowreport > 0 ORDER BY name + 0 + 99 + 10 + + + + + + + + diff --git a/packages/ep_products/Configuration/FlexForms/flexform_teammembers.xml b/packages/ep_products/Configuration/FlexForms/flexform_teammembers.xml new file mode 100644 index 0000000..cd68655 --- /dev/null +++ b/packages/ep_products/Configuration/FlexForms/flexform_teammembers.xml @@ -0,0 +1,53 @@ + + + 1 + + + + + + Konfiguration + + array + + + + 0 + + + input + 20 + required + + + + + + 0 + + + input + 20 + required + + + + + + 0 + + + select + selectMultipleSideBySide + 1 + sys_category + 0 + 99 + + + + + + + + diff --git a/packages/ep_products/Configuration/RequestMiddlewares.php b/packages/ep_products/Configuration/RequestMiddlewares.php new file mode 100644 index 0000000..f8ec710 --- /dev/null +++ b/packages/ep_products/Configuration/RequestMiddlewares.php @@ -0,0 +1,39 @@ + [ + 'ep/staticsearch' => [ + 'target' => \EP\EpProducts\Middleware\StaticSearchParamsMiddleware::class, + 'before' => [ + 'typo3/cms-redirects/redirecthandler', + ], + 'after' => [ + 'typo3/cms-frontend/static-route-resolver', + ], + ], + 'ep/productapi' => [ + 'target' => \EP\EpProducts\Middleware\ApiMiddleware::class, + 'before' => [ + 'typo3/cms-frontend/page-resolver', + ], + 'after' => [ + 'typo3/cms-frontend/site', + ], + ], + 'ep/content-page-type-guard' => [ + 'target' => \EP\EpProducts\Middleware\ContentPageTypeGuardMiddleware::class, + 'after' => [ + 'typo3/cms-frontend/page-resolver', + ], + 'before' => [ + 'typo3/cms-frontend/page-argument-validator', + ], + ], + 'ep/staging-environment' => [ + 'target' => \EP\EpProducts\Middleware\StagingEnvironmentMiddleware::class, + 'after' => [ + 'typo3/cms-frontend/authentication', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/Services.yaml b/packages/ep_products/Configuration/Services.yaml new file mode 100644 index 0000000..2d88679 --- /dev/null +++ b/packages/ep_products/Configuration/Services.yaml @@ -0,0 +1,61 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + EP\EpProducts\: + resource: '../Classes/*' + exclude: '../Classes/Domain/Model/*' + + EP\EpProducts\EventListener\SystemInformationToolbarListener: + tags: + - name: event.listener + identifier: 'epToolbarListener' + event: TYPO3\CMS\Backend\Backend\Event\SystemInformationToolbarCollectorEvent + + cache.ep_cache: + class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface + public: true + factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache'] + arguments: ['ep_products_cache'] + + EP\EpProducts\Service\HotelImageService: + arguments: + $cache: '@cache.ep_cache' + + EP\EpProducts\Service\ProductImageService: + arguments: + $cache: '@cache.ep_cache' + + EP\EpProducts\Controller\AjaxCalendarController: + arguments: + $cache: '@cache.ep_cache' + + EP\EpProducts\Service\MyEpImageService: + arguments: + $cache: '@cache.ep_cache' + + ep_products.image_service.hotel: + class: EP\EpProducts\Service\MyEpImageService + arguments: + $cache: '@cache.ep_cache' + $table: 'tx_epproducts_domain_model_hotel' + + ep_products.image_service.product: + class: EP\EpProducts\Service\MyEpImageService + arguments: + $cache: '@cache.ep_cache' + $table: 'tx_epproducts_domain_model_product' + + ep_products.image_service.region: + class: EP\EpProducts\Service\MyEpImageService + arguments: + $cache: '@cache.ep_cache' + $table: 'tx_epproducts_domain_model_region' + + EP\EpProducts\Middleware\ApiMiddleware: + arguments: + $hotelImageService: '@ep_products.image_service.hotel' + $productImageService: '@ep_products.image_service.product' + $regionImageService: '@ep_products.image_service.region' diff --git a/packages/ep_products/Configuration/TCA/Overrides/sys_domain.php b/packages/ep_products/Configuration/TCA/Overrides/sys_domain.php new file mode 100644 index 0000000..ca778b0 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/Overrides/sys_domain.php @@ -0,0 +1,36 @@ + [ + 'label' => 'Vertragspartner', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_reseller', + 'foreign_table_where' => 'ORDER BY title', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + ]; + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns( + 'sys_domain', + $temporaryColumns + ); + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( + 'sys_domain', + 'tx_epproducts_reseller', + '', + 'before:redirectTo' + ); + +}); diff --git a/packages/ep_products/Configuration/TCA/Overrides/sys_template.php b/packages/ep_products/Configuration/TCA/Overrides/sys_template.php new file mode 100644 index 0000000..b0c3bc1 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/Overrides/sys_template.php @@ -0,0 +1,9 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_badge', + 'default_sortby' => 'ORDER BY title', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,subtitle', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, title, subtitle, link', + ], + ], + 'palettes' => [ + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_badge', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_badge.pid=###CURRENT_PID### AND tx_epproducts_domain_model_badge.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => 0, + 'label' => 'Titel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'subtitle' => [ + 'exclude' => 0, + 'label' => 'Untertitel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'link' => [ + 'exclude' => 0, + 'label' => 'Link', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_city.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_city.php new file mode 100644 index 0000000..2e2967a --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_city.php @@ -0,0 +1,470 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_city', + 'default_sortby' => 'ORDER BY name', + 'label' => 'name_internal', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,headline,teaser,description,party,leisure,teaser_images,header_images, images,latitude,longitude,country,region,facts,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, name_internal, --palette--;;name, --palette--;;destinations, teaser, + keywords, description, description_extended, party, leisure, facts, officetip, + --div--;Bilder/Dateien, teaser_images, header_images, images', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden,detail_page,exclude_from_teasergroups'], + 'name' => ['showitem' => 'name,headline,--linebreak--,header_title,header_subtitle'], + 'destinations' => ['showitem' => 'country,region,city,latitude,longitude,season'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_city', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_city.pid=###CURRENT_PID### AND tx_epproducts_domain_model_city.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'exclude_from_teasergroups' => [ + 'exclude' => 0, + 'label' => 'Nicht in Teasergruppen', + 'config' => [ + 'type' => 'check', + ], + ], + 'season' => [ + 'exclude' => 0, + 'label' => 'Saison', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Winter', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_WINTER], + ['Sommer', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_SUMMER], + ], + ] + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'name_internal' => [ + 'exclude' => 0, + 'label' => 'Name intern', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords für Suchschlitz', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Der Ort (oben)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description_extended' => [ + 'exclude' => 0, + 'label' => 'Der Ort (unten)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'party' => [ + 'exclude' => 0, + 'label' => 'Party', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'leisure' => [ + 'exclude' => 0, + 'label' => 'Freizeitangebot', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'Teaser', + 'value' => 2, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header', + 'value' => 3.8, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'latitude' => [ + 'exclude' => 0, + 'label' => 'Breitengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'longitude' => [ + 'exclude' => 0, + 'label' => 'Längengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'region' => [ + 'exclude' => 0, + 'label' => 'Gebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'officetip' => [ + 'exclude' => 0, + 'label' => 'Officetipp', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_officetip', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'facts' => [ + 'exclude' => 0, + 'label' => 'Facts', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'MM' => 'tx_epproducts_city_fact_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_fact', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_concept.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_concept.php new file mode 100644 index 0000000..f03196a --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_concept.php @@ -0,0 +1,415 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_concept', + 'default_sortby' => 'ORDER BY name', + 'sortby' => 'sorting', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,description,detail_page,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, --palette--;;naming, --palette--;;header, teaser, description, + description_extended, + --div--;Bilder/Dateien, teaser_images, header_images, images, + --div--;Popup, popup_text, --palette--;;popup', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden,detail_page,code'], + 'naming' => ['showitem' => 'name,headline'], + 'header' => ['showitem' => 'header_title,header_subtitle'], + 'popup' => ['showitem' => 'popup_title, popup_button_label'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_concept', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_concept.pid=###CURRENT_PID### AND tx_epproducts_domain_model_concept.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + + 'bus_pro_id' => [ + 'exclude' => 0, + 'label' => 'BusPro ID', + 'config' => [ + 'type' => 'none', + 'format' => 'int', + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 5, + 'eval' => 'trim,lower,required' + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Konzeptbeschreibung (oben)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description_extended' => [ + 'exclude' => 0, + 'label' => 'Konzeptbeschreibung (unten)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'popup_title' => [ + 'exclude' => 0, + 'label' => 'Popup Titel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => 'Hinweis', + ], + ], + 'popup_text' => [ + 'exclude' => 0, + 'label' => 'Popup Text', + 'description' => 'Wenn ausgefüllt, wird ein Popup mit diesem Text beim Anklicken eines Teasers angezeigt.', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 5, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'popup_button_label' => [ + 'exclude' => 0, + 'label' => 'Popup Button Label', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => 'Ok', + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaserEven' => [ + 'title' => 'Teaser gerade', + 'value' => 1.3333333333333334, + ], + 'teaserOdd' => [ + 'title' => 'Teaser ungerade', + 'value' => 2.0555555555555556, + ], + 'free' => [ + 'title' => 'Frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header groß', + 'value' => 2.3333333333333334, + ], + 'headerSmall' => [ + 'title' => 'Header klein', + 'value' => 3.3333333333333334, + ], + 'free' => [ + 'title' => 'Frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'products' => [ + 'exclude' => 0, + 'label' => 'Produkte', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_contingent.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_contingent.php new file mode 100644 index 0000000..2567d13 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_contingent.php @@ -0,0 +1,236 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_contingent', + 'label' => 'date', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getContingentLabel', + 'default_sortby' => 'ORDER BY date', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => false, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'room_code,room_label', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => 'date, pax, available, status, min_price, hotel, room_code, room_label, groupsch_id'], + ], + 'palettes' => [], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_contingent', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_contingent.pid=###CURRENT_PID### AND tx_epproducts_domain_model_contingent.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'pax' => [ + 'exclude' => 0, + 'label' => 'Pax', + 'config' => [ + 'type' => 'input', + 'size' => 3, + 'eval' => 'int,required' + ], + ], + 'available' => [ + 'exclude' => 0, + 'label' => 'verfügbar', + 'config' => [ + 'type' => 'input', + 'size' => 3, + 'eval' => 'int,required' + ], + ], + 'date' => [ + 'exclude' => 0, + 'label' => 'Datum', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date,required', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ], + ], + 'hotel' => [ + 'exclude' => 0, + 'label' => 'Unterkunft', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'status' => [ + 'exclude' => 0, + 'label' => 'Status', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'minitems' => 1, + 'maxitems' => 1, + 'items' => [ + [ 'Ok', \EP\EpProducts\Domain\Model\Contingent::STATUS_OK ], + [ 'Buchungsstop', \EP\EpProducts\Domain\Model\Contingent::STATUS_BLOCKED ], + [ 'auf Anfrage', \EP\EpProducts\Domain\Model\Contingent::STATUS_ONREQUEST ], + ] + ], + ], + 'min_price' => [ + 'exclude' => 0, + 'label' => 'ab Preis', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'min_nights' => [ + 'exclude' => 0, + 'label' => 'ab Preis Nächte', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'additional_night_min_price' => [ + 'exclude' => 0, + 'label' => 'ab Preis Verlängerungsnacht', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'additional_night_min_nights' => [ + 'exclude' => 0, + 'label' => 'Verlängerungsnacht min. Nächte', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'groupsch_id' => [ + 'exclude' => 0, + 'label' => 'groups.ch ID', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'trim,int' + ], + ], + 'room_code' => [ + 'exclude' => 0, + 'label' => 'Zimmercode', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'trim' + ], + ], + 'room_label' => [ + 'exclude' => 0, + 'label' => 'Zimmerbezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_country.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_country.php new file mode 100644 index 0000000..e947bab --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_country.php @@ -0,0 +1,444 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_country', + 'label' => 'name_internal', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name_internal', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'code,name,headline,teaser,description,teaser_images,images,latitude,longitude,facts,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => ' + --palette--;;top, name_internal, --palette--;;name, headline, --palette--;;geolocation, keywords, + --div--;Texte, teaser, description, description_extended, + --div--;Verknüpfungen, facts, snowreport, officetip, + --div--;Bilder/Dateien, teaser_images, header_images, images', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden,detail_page'], + 'name' => ['showitem' => 'code,name,--linebreak--,header_title,header_subtitle'], + 'geolocation' => ['showitem' => 'latitude,longitude,season'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_country.pid=###CURRENT_PID### AND tx_epproducts_domain_model_country.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 2, + 'eval' => 'trim,required' + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'name_internal' => [ + 'exclude' => 0, + 'label' => 'Name intern', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'season' => [ + 'exclude' => 0, + 'label' => 'Saison', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Winter', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_WINTER], + ['Sommer', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_SUMMER], + ], + ] + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords für Suchschlitz', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Beschreibung (oben)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description_extended' => [ + 'exclude' => 0, + 'label' => 'Beschreibung (unten)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'Teaser', + 'value' => 2, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header', + 'value' => 3.8, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'latitude' => [ + 'exclude' => 0, + 'label' => 'Breitengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'longitude' => [ + 'exclude' => 0, + 'label' => 'Längengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'officetip' => [ + 'exclude' => 0, + 'label' => 'Officetipp', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_officetip', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'snowreport' => [ + 'exclude' => 0, + 'label' => 'Gebiet für Schneehöhen', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'items' => [ + ['Keine Zuordnung', 0] + ], + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_snowreport', + 'foreign_table_where' => 'ORDER BY tx_epproducts_domain_model_snowreport.region_name', + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'facts' => [ + 'exclude' => 0, + 'label' => 'Facts', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'MM' => 'tx_epproducts_country_fact_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_fact', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_date.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_date.php new file mode 100644 index 0000000..473e602 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_date.php @@ -0,0 +1,446 @@ + [ + 'title' => 'Termin', + 'label' => 'bus_pro_id', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getDateLabel', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => false, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'bus_pro_id,code,date_start,date_end,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => 'hidden, bus_pro_id, hotel_bus_pro_id, code, daytrip, date_start, date_end, nights, board, + board_code, board_bus_pro_id, bus_included, bus_available, skipass_included, group_travel, + services_included, services_general, courses_included, guide, pickups, discount, min_price, pseudo_price, available, min_pax, + new' + ], + ], + 'palettes' => [ + '1' => ['showitem' => ''], + ], + 'columns' => [ + 'sys_language_uid' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_date', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_date.pid=###CURRENT_PID### AND tx_epproducts_domain_model_date.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'product' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hotel' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'country' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'region' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'city' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + // Denormalized columns written by DateImportService, which rebuilds this table + // wholesale. They are TEXT NOT NULL, and MySQL cannot default that, so without a + // TCA entry DataHandler omits them and every INSERT fails under STRICT_TRANS_TABLES. + 'product_keywords' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'product_teaser' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'concept_popup_text' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'country_keywords' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'region_keywords' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'city_keywords' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hotel_keywords' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hotel_teaser' => [ + 'exclude' => true, + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'bus_pro_id' => [ + 'exclude' => false, + 'label' => 'BusPro ID', + 'config' => [ + 'type' => 'none', + 'format' => 'int', + ], + ], + 'hotel_bus_pro_id' => [ + 'exclude' => false, + 'label' => 'Hotel BusPro ID', + 'config' => [ + 'type' => 'none', + 'format' => 'int', + ], + ], + 'code' => [ + 'exclude' => false, + 'label' => 'Kürzel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'daytrip' => [ + 'exclude' => false, + 'label' => 'tageweise buchbar', + 'config' => [ + 'type' => 'check', + 'default' => 0 + ] + ], + 'season' => [ + 'exclude' => false, + 'label' => 'Saison', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + [ 'Winter', 'w' ], + [ 'Sommer', 's' ], + ], + 'default' => 'w', + ], + ], + 'date_start' => [ + 'exclude' => false, + 'label' => 'Datum von', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ], + ], + 'date_end' => [ + 'exclude' => false, + 'label' => 'Datum bis', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ], + ], + 'nights' => [ + 'exclude' => false, + 'label' => 'Nächte', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'board' => [ + 'exclude' => false, + 'label' => 'Verpflegung', + 'config' => [ + 'type' => 'none', + ], + ], + 'board_code' => [ + 'exclude' => false, + 'label' => 'Verpflegung Code', + 'config' => [ + 'type' => 'none', + ], + ], + 'board_bus_pro_id' => [ + 'exclude' => false, + 'label' => 'Verpflegung BusPro ID', + 'config' => [ + 'type' => 'none', + 'format' => 'int', + ], + ], + 'bus_included' => [ + 'exclude' => false, + 'label' => 'Bus inklusive', + 'config' => [ + 'type' => 'check', + 'default' => 0 + ] + ], + 'bus_available' => [ + 'exclude' => false, + 'label' => 'Bus verfügbar', + 'config' => [ + 'type' => 'check', + 'default' => 0 + ] + ], + 'skipass_included' => [ + 'exclude' => false, + 'label' => 'Skipass inklusive', + 'config' => [ + 'type' => 'check', + 'default' => 0 + ] + ], + 'group_travel' => [ + 'exclude' => false, + 'label' => 'Gruppenreise', + 'config' => [ + 'type' => 'check', + 'default' => 0 + ] + ], + 'bus_price' => [ + 'exclude' => false, + 'label' => 'Bus Preis', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'services_included' => [ + 'exclude' => false, + 'label' => 'Inklusivleistungen', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'services_general' => [ + 'exclude' => false, + 'label' => 'Leistungen', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'courses_included' => [ + 'exclude' => false, + 'label' => 'Kurse', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'guide' => [ + 'exclude' => false, + 'label' => 'Reiseleiter', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'pickups' => [ + 'exclude' => false, + 'label' => 'Zustiege', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'discount' => [ + 'exclude' => false, + 'label' => 'Rabatt', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'min_price' => [ + 'exclude' => false, + 'label' => 'ab Preis', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'pseudo_price' => [ + 'exclude' => false, + 'label' => 'Pseudopreis', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'available' => [ + 'exclude' => false, + 'label' => 'verfügbar', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'min_pax' => [ + 'exclude' => false, + 'label' => 'Min. Pax', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'new' => [ + 'exclude' => false, + 'label' => 'neu', + 'config' => [ + 'type' => 'check', + 'default' => 0, + ], + ], + ] +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_fact.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_fact.php new file mode 100644 index 0000000..16716e1 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_fact.php @@ -0,0 +1,145 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_fact', + 'label' => 'label', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY label', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,label,keywords', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => 'hidden, label, name'], + ], + 'palettes' => [ + '1' => ['showitem' => ''], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_fact.pid=###CURRENT_PID### AND tx_epproducts_domain_model_fact.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'label' => [ + 'exclude' => 0, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords für Suchschlitz', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_faq.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_faq.php new file mode 100644 index 0000000..a72ded0 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_faq.php @@ -0,0 +1,170 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_faq', + 'label' => 'label', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'question,answer,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, category, label, question, answer', + ], + ], + 'palettes' => [ + '1' => ['showitem' => ''], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_faq', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_faq.pid=###CURRENT_PID### AND tx_epproducts_domain_model_faq.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'category' => [ + 'exclude' => 0, + 'label' => 'Kategorie', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + [ '', '' ], + [ 'Buchung & Reiseablauf', 'Buchung & Reiseablauf' ], + [ 'Skipass, Skigebiet & Kurse', 'Skipass, Skigebiet & Kurse' ], + [ 'Änderungen & Zusatzleistungen', 'Änderungen & Zusatzleistungen' ], + [ 'Anreise & Transport', 'Anreise & Transport' ], + [ 'Unterkunft & Sportclubs', 'Unterkunft & Sportclubs' ], + [ 'Programm & Aktivitäten', 'Programm & Aktivitäten' ], + [ 'Eventreisen', 'Eventreisen' ], + [ 'Teilnahmebedingungen', 'Teilnahmebedingungen' ], + [ 'Zahlung & Rechnung', 'Zahlung & Rechnung' ], + [ 'Kontakt & Erreichbarkeit', 'Kontakt & Erreichbarkeit' ], + [ 'Stornierung, Reklamation & Versicherung', 'Stornierung, Reklamation & Versicherung' ], + ], + 'default' => '', + ], + ], + 'label' => [ + 'exclude' => 0, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'question' => [ + 'exclude' => 0, + 'label' => 'Frage', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'answer' => [ + 'exclude' => 0, + 'label' => 'Antwort', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim,required', + 'enableRichtext' => true, + ] + ], + + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php new file mode 100644 index 0000000..a2acf0e --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceboard.php @@ -0,0 +1,151 @@ + [ + 'title' => 'Verpflegungsleistung', + 'default_sortby' => 'ORDER BY price', + 'sortby' => 'sorting', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + 'hideTable' => true, + ], + 'types' => [ + '1' => [ + 'showitem' => 'title, price, price_chf', + ], + ], + 'palettes' => [ + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => -1, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceboard', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_groupspriceboard.pid=###CURRENT_PID### AND tx_epproducts_domain_model_groupspriceboard.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 32, + 'eval' => 'required', + ] + ], + 'price' => [ + 'exclude' => false, + 'label' => 'Preis pro Person/Nacht', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2,required', + ] + ], + 'price_chf' => [ + 'exclude' => false, + 'label' => 'Preis pro Person/Nacht in CHF', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2', + ] + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceconfig.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceconfig.php new file mode 100644 index 0000000..1be71f2 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceconfig.php @@ -0,0 +1,186 @@ + [ + 'title' => 'Gruppenhaus Preis', + 'default_sortby' => 'ORDER BY date_from', + 'label' => 'date_from', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getGroupsPriceConfigLabel', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'date_from', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + 'hideTable' => true, + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;dates, --palette--;;included, --palette--;;prices, --palette--;;prices_groups', + ], + ], + 'palettes' => [ + 'dates' => ['showitem' => 'date_from, date_to'], + 'included' => ['showitem' => 'persons_included'], + 'prices' => ['showitem' => 'price, price_chf, price_additional_person, price_additional_person_chf'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => -1, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceconfig', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_groupspriceconfig.pid=###CURRENT_PID### AND tx_epproducts_domain_model_groupspriceconfig.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'date_from' => [ + 'exclude' => false, + 'label' => 'Datum von', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'date,required', + 'renderType' => 'inputDateTime', + ] + ], + 'date_to' => [ + 'exclude' => false, + 'label' => 'Datum bis', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'date,required', + 'renderType' => 'inputDateTime', + ] + ], + 'persons_included' => [ + 'exclude' => false, + 'label' => 'Inklusiv-Personen', + 'config' => [ + 'type' => 'input', + 'size' => 4, + 'eval' => 'int,required', + ] + ], + 'price' => [ + 'exclude' => false, + 'label' => 'Preis pro Nacht', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2,required', + ] + ], + 'price_chf' => [ + 'exclude' => false, + 'label' => 'Preis pro Nacht in CHF', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2', + ] + ], + 'price_additional_person' => [ + 'exclude' => false, + 'label' => 'Preis weitere Person', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2', + ] + ], + 'price_additional_person_chf' => [ + 'exclude' => false, + 'label' => 'Preis weitere Person in CHF', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2', + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php new file mode 100644 index 0000000..ffe085d --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_groupspriceoption.php @@ -0,0 +1,175 @@ + [ + 'title' => 'Gruppenhaus Zusatzleistung', + 'default_sortby' => 'ORDER BY price', + 'sortby' => 'sorting', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + 'hideTable' => true, + ], + 'types' => [ + '1' => [ + 'showitem' => 'title, --palette--;;priceconfig', + ], + ], + 'palettes' => [ + 'priceconfig' => ['showitem' => 'price, price_chf, type'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => -1, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceoption', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_groupspriceoption.pid=###CURRENT_PID### AND tx_epproducts_domain_model_groupspriceoption.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 32, + 'eval' => 'required', + ] + ], + 'price' => [ + 'exclude' => false, + 'label' => 'Preis', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2,required', + ] + ], + 'price_chf' => [ + 'exclude' => false, + 'label' => 'Preis in CHF', + 'config' => [ + 'type' => 'input', + 'size' => 8, + 'eval' => 'double2', + ] + ], + 'type' => [ + 'exclude' => false, + 'label' => 'Typ', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['pauschal', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_GLOBAL], + ['pro Person', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_PER_PAX], + ['pro Nacht', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_PER_NIGHT], + ['pro Person und Nacht', \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_PER_PAX_AND_NIGHT], + ], + 'default' => \EP\EpProducts\Domain\Model\GroupsPriceOption::TYPE_GLOBAL, + ] + ], + 'ignore_with_board' => [ + 'exclude' => false, + 'label' => 'Entfällt bei Verpflegung', + 'config' => [ + 'type' => 'check', + 'default' => 0, + ] + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php new file mode 100644 index 0000000..bdf1d97 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel.php @@ -0,0 +1,850 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_hotel', + 'default_sortby' => 'ORDER BY name', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getHotelLabel', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,headline,teaser,description,features,room_types,additional_information,teaser_images, + header_images,images,reseller_images,code,type,category,country,region,city,teamer,products,external_link', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, --palette--;;name, --palette--;;geolocation, + --palette--;;destinations, --palette--;;categories, keywords, + --div--;Texte, teaser, description, features, room_types, additional_information, + --div--;Verknüpfungen, facts, teamer, additional_codes, mappings, + --div--;Bilder/Dateien, teaser_images, header_images, images, reseller_images, + --div--;Gruppenhaus Icons, --palette--;;icons', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden,earlybird,new,low_contingent,show_as_teaser, + --linebreak--,code,detail_page,--linebreak--,external_link,path_segment'], + 'geolocation' => ['showitem' => 'address, longitude, latitude'], + 'destinations' => ['showitem' => 'country,region,city'], + 'name' => ['showitem' => 'name,short_name,--linebreak--,header_title,header_subtitle,--linebreak--,headline'], + 'categories' => ['showitem' => 'type,category'], + 'season' => ['showitem' => 'main_season_from, main_season_to'], + 'others' => ['showitem' => 'season,groups_price_tax_label, groups_price_adolescents_age'], + 'icons' => ['showitem' => 'beds_count,parking_count,altitude,shower,partyroom,gamesroom,sauna,wifi,distance_bus,distance_lift'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_hotel.pid=###CURRENT_PID### AND tx_epproducts_domain_model_hotel.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'new' => [ + 'exclude' => 0, + 'label' => 'Neu', + 'config' => [ + 'type' => 'check', + ], + ], + 'earlybird' => [ + 'exclude' => 0, + 'label' => 'Frühbucher', + 'config' => [ + 'type' => 'check', + ], + ], + 'low_contingent' => [ + 'exclude' => 0, + 'label' => 'wenige Betten', + 'config' => [ + 'type' => 'check', + ], + ], + 'show_as_teaser' => [ + 'exclude' => 0, + 'label' => 'als Teaser anzeigen', + 'config' => [ + 'type' => 'check', + ], + ], + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'text', + 'cols' => 30, + 'rows' => 2, + 'eval' => 'trim,required' + ], + ], + 'short_name' => [ + 'exclude' => 0, + 'label' => 'Kurzname', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords für Suchschlitz', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Die Unterkunft', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'features' => [ + 'exclude' => 0, + 'label' => 'Ausstattung', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'room_types' => [ + 'exclude' => 0, + 'label' => 'Zimmerarten/Apartmentbeschreibung', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'additional_information' => [ + 'exclude' => 0, + 'label' => 'Zusatzinformationen', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'Teaser', + 'value' => 2, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header', + 'value' => 3.8, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'reseller_images' => [ + 'exclude' => 0, + 'label' => 'Bilder für Vertriebspartner', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'reseller_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'external_link' => [ + 'exclude' => 0, + 'label' => 'Externer Link (Google MyBusiness)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'renderType' => 'inputLink', + ], + ], + 'type' => [ + 'exclude' => 0, + 'label' => 'Typ', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Unbestimmt', 0], + ['Ferienwohnung', \EP\EpProducts\Domain\Model\Hotel::TYPE_HOLIDAY_FLAT], + ['Sportclub', \EP\EpProducts\Domain\Model\Hotel::TYPE_SPORTS_CLUB], + ['Apartment', \EP\EpProducts\Domain\Model\Hotel::TYPE_APPARTMENT], + ['Gästehaus', \EP\EpProducts\Domain\Model\Hotel::TYPE_GUESTHOUSE], + ['Pension', \EP\EpProducts\Domain\Model\Hotel::TYPE_PENSION], + ], + 'size' => 1, + 'minItems' => 1, + 'maxItems' => 1 + ], + ], + 'category' => [ + 'exclude' => 0, + 'label' => 'Kategorie', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Unbestimmt', 0], + ['Standard', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_STANDARD], + ['Basic', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_BASIC], + ['Komfort', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_COMFORT], + ['Deluxe', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_DELUXE], + ['Super Deluxe', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_SUPER_DELUXE], + ], + 'size' => 1, + 'minItems' => 1, + 'maxItems' => 1 + ] + ], + 'country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'region' => [ + 'exclude' => 0, + 'label' => 'Gebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'city' => [ + 'exclude' => 0, + 'label' => 'Ort', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_city', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'facts' => [ + 'exclude' => 0, + 'label' => 'Facts', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'MM' => 'tx_epproducts_hotel_fact_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_fact', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + 'products' => [ + 'exclude' => 0, + 'label' => 'Produkte', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'MM' => 'tx_epproducts_product_hotel_mm', + 'MM_opposite_field' => 'hotels', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + ], + ], + 'teamer' => [ + 'exclude' => 0, + 'label' => 'Teamer', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_teammember', + 'MM' => 'tx_epproducts_hotel_teammember_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 1, + ], + ], + 'additional_codes' => [ + 'exclude' => 0, + 'label' => 'Zusätzliche Codes', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_matchcode', + 'foreign_field' => 'entity', + 'foreign_match_fields' => [ + 'entity_type' => 'hotel', + ], + 'maxitems' => 10, + 'appearance' => [ + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0, + 'expandSingle' => 1, + ], + ], + ], + 'mappings' => [ + 'exclude' => 0, + 'label' => 'abweichende Detailseiten', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_hotel_mapping', + 'foreign_field' => 'hotel', + 'maxitems' => 10, + 'appearance' => [ + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0, + 'expandSingle' => 1, + ], + ], + ], + 'latitude' => [ + 'exclude' => 0, + 'label' => 'Breitengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'longitude' => [ + 'exclude' => 0, + 'label' => 'Längengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'address' => [ + 'exclude' => 0, + 'label' => 'Anschrift', + 'config' => [ + 'type' => 'text', + 'cols' => 30, + 'rows' => 5, + 'eval' => 'trim', + ], + ], + 'path_segment' => [ + 'exclude' => 0, + 'label' => 'URL Segment', + 'config' => [ + 'type' => 'slug', + 'generatorOptions' => [ + 'fields' => ['name'], + 'prefixParentPageSlug' => false, + 'replacements' => [ + '/' => '-', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + ], + ], + 'groups_price_configs' => [ + 'exclude' => 0, + 'label' => 'Preiskonfigurationen', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceconfig', + 'foreign_field' => 'hotel', + 'maxitems' => 99, + 'minitems' => 0, + 'appearance' => [ + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0, + 'expandSingle' => 1, + ], + ], + ], + 'groups_price_boards' => [ + 'exclude' => 0, + 'label' => 'Verpflegungsleistungen', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceboard', + 'foreign_field' => 'hotel', + 'foreign_sortby' => 'sorting', + 'maxitems' => 99, + 'minitems' => 0, + 'appearance' => [ + 'useSortable' => true, + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0, + 'expandSingle' => 1, + ], + ], + ], + 'groups_price_options' => [ + 'exclude' => 0, + 'label' => 'Optionale Zusatzleistungen', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_groupspriceoption', + 'foreign_field' => 'hotel', + 'foreign_sortby' => 'sorting', + 'maxitems' => 99, + 'minitems' => 0, + 'appearance' => [ + 'useSortable' => true, + 'collapseAll' => 1, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => 0, + 'showPossibleLocalizationRecords' => 0, + 'showAllLocalizationLink' => 0, + 'expandSingle' => 1, + ], + ], + ], + 'groups_price_tax_label' => [ + 'exclude' => 0, + 'label' => 'Text Ortstaxe', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + 'default' => 'zzgl. vorab zu entrichtender Ortstaxe', + ], + ], + 'groups_price_adolescents_age' => [ + 'exclude' => 0, + 'label' => 'Altersgrenze Jugendliche (6-X Jahre)', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'int', + ], + ], + 'main_season_from' => [ + 'exclude' => false, + 'label' => 'Hauptsaison von', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ], + ], + 'main_season_to' => [ + 'exclude' => false, + 'label' => 'Hauptsaison bis', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ], + ], + 'beds_count' => [ + 'exclude' => 0, + 'label' => 'Anzahl Betten', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'int', + ], + ], + 'parking_count' => [ + 'exclude' => 0, + 'label' => 'Anzahl Parkplätze', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'int', + ], + ], + 'altitude' => [ + 'exclude' => 0, + 'label' => 'Höhe', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + ], + ], + 'shower' => [ + 'exclude' => 0, + 'label' => 'Dusche', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + ], + ], + 'partyroom' => [ + 'exclude' => 0, + 'label' => 'Partyraum', + 'config' => [ + 'type' => 'check', + ], + ], + 'gamesroom' => [ + 'exclude' => 0, + 'label' => 'Spieleraum', + 'config' => [ + 'type' => 'check', + ], + ], + 'sauna' => [ + 'exclude' => 0, + 'label' => 'Sauna', + 'config' => [ + 'type' => 'check', + ], + ], + 'wifi' => [ + 'exclude' => 0, + 'label' => 'WiFi', + 'config' => [ + 'type' => 'check', + ], + ], + 'distance_bus' => [ + 'exclude' => 0, + 'label' => 'Entfernung zum Bus', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + ], + ], + 'distance_lift' => [ + 'exclude' => 0, + 'label' => 'Entfernung zum Lift', + 'config' => [ + 'type' => 'input', + 'size' => 50, + 'eval' => 'trim', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel_mapping.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel_mapping.php new file mode 100644 index 0000000..3e2726c --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_hotel_mapping.php @@ -0,0 +1,141 @@ + [ + 'title' => 'Hotelmapping', + 'label' => 'product', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'product', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + 'hideTable' => true, + ], + 'types' => [ + '1' => [ + 'showitem' => 'product,detail_page', + ], + ], + 'palettes' => [], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_hotel_mapping', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_hotel_mapping.pid=###CURRENT_PID### AND tx_epproducts_domain_model_hotel_mapping.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'product' => [ + 'exclude' => 0, + 'label' => 'Produkt', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'minitems' => 1, + 'maxitems' => 1, + 'multiple' => 0, + ], + ], + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'hotel' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_infobox.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_infobox.php new file mode 100644 index 0000000..3317085 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_infobox.php @@ -0,0 +1,145 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_infobox', + 'default_sortby' => 'ORDER BY title', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,text', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, title, text', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden, default_text'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_infobox', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_infobox.pid=###CURRENT_PID### AND tx_epproducts_domain_model_infobox.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => 0, + 'label' => 'Titel (nur intern)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'text' => [ + 'exclude' => 0, + 'label' => 'Text', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim,required', + 'enableRichtext' => true, + ] + ], + 'default_text' => [ + 'exclude' => 0, + 'label' => 'Default', + 'config' => [ + 'type' => 'check', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_journey.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_journey.php new file mode 100644 index 0000000..5413972 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_journey.php @@ -0,0 +1,170 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_journey', + 'default_sortby' => 'ORDER BY name', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,by_car,by_bus,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, name, by_car, by_bus, image', + ], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_journey', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_journey.pid=###CURRENT_PID### AND tx_epproducts_domain_model_journey.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'by_car' => [ + 'exclude' => 0, + 'label' => 'PKW/Eigenanreise', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'by_bus' => [ + 'exclude' => 0, + 'label' => 'Busanreise', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'image' => [ + 'exclude' => 0, + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 0, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_matchcode.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_matchcode.php new file mode 100644 index 0000000..cf92888 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_matchcode.php @@ -0,0 +1,139 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_matchcode', + 'label' => 'code', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => false, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'code', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => 'code'], + ], + 'palettes' => [ + '1' => ['showitem' => ''], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_matchcode', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_matchcode.pid=###CURRENT_PID### AND tx_epproducts_domain_model_matchcode.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'entity_type' => [ + 'exclude' => 0, + 'label' => 'Entität', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + [ + 'Hotel', + 'hotel', + ], + ], + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_officetip.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_officetip.php new file mode 100644 index 0000000..0bc185b --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_officetip.php @@ -0,0 +1,147 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_officetip', + 'label' => 'label', + 'prependAtCopy' => '[COPY %s]', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getOfficetipLabel', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'label,text,author', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => '--palette--;;top, text'], + ], + 'palettes' => [ + 'top' => ['showitem' => 'label,author,hidden'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_officetip', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_officetip.pid=###CURRENT_PID### AND tx_epproducts_domain_model_officetip.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'label' => [ + 'exclude' => 0, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'text' => [ + 'exclude' => 0, + 'label' => 'Text', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim,required' + ] + ], + 'author' => [ + 'exclude' => 0, + 'label' => 'Autor', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_teammember', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_product.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_product.php new file mode 100644 index 0000000..a7cd7b2 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_product.php @@ -0,0 +1,951 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_product', + 'label' => 'name_internal', + 'prependAtCopy' => '[COPY %s]', + 'label_userFunc' => \EP\EpProducts\Service\LabelService::class . '->getProductLabel', + 'default_sortby' => 'ORDER BY name_internal', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,headline', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, name_internal, --palette--;;name, --palette--;;header, + --palette--;;destinations, --palette--;;misc, --palette--;Alternative;alternative, + --div--;Texte, teaser, teaser_long, concept_description, board_description, additional_services, + included_services, programme_description, ski_pass_alt_title, ski_pass_description, + non_bookable_info, + --div--;Verknüpfungen, --palette--;;records, hotels, facts, faqs, additional_regions, + --div--;Bilder/Dateien, teaser_images, header_images, images, reseller_images, banner, video, + tour_thumbnail, + --div--;Bewertung, --palette--;;rating, testimonials, + --div--;Buchbarkeit, non_bookable_dates', + ], + ], + 'palettes' => [ + 'destinations' => ['showitem' => 'country,region,city'], + 'top' => ['showitem' => 'searchable, bookable, hide_booking_button, daytrip, important, skipass_included, + season, detail_page, external_link, tour_link, path_segment, min_price'], + 'name' => ['showitem' => 'name,code'], + 'alternative' => ['showitem' => 'alt_product, alt_label'], + 'header' => ['showitem' => 'headline, subline, --linebreak--, header_title, header_subtitle'], + 'rating' => ['showitem' => 'rating_average, rating_votes_count'], + 'records' => ['showitem' => 'journey, concept, calendar_hotel, officetip, badge'], + 'misc' => ['showitem' => 'feature,keywords'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_product.pid=###CURRENT_PID### AND tx_epproducts_domain_model_product.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_product.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'searchable' => [ + 'exclude' => 0, + 'label' => 'erscheint im Suchergebnis', + 'config' => [ + 'type' => 'check', + ], + ], + 'bookable' => [ + 'exclude' => 0, + 'label' => 'online buchbar', + 'config' => [ + 'type' => 'check', + ], + ], + 'hide_booking_button' => [ + 'exclude' => 0, + 'label' => 'Buchungsbutton verstecken', + 'config' => [ + 'type' => 'check', + ], + ], + 'daytrip' => [ + 'exclude' => 0, + 'label' => 'tageweise buchbar', + 'config' => [ + 'type' => 'check', + ], + ], + 'important' => [ + 'exclude' => 0, + 'label' => 'in Teasergruppen immer vorne', + 'config' => [ + 'type' => 'check', + ], + ], + 'skipass_included' => [ + 'exclude' => 0, + 'label' => 'Skipass inklusive', + 'config' => [ + 'type' => 'check', + 'default' => true, + ], + ], + 'season' => [ + 'exclude' => 0, + 'label' => 'Saison', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + [ 'Winter', 'w' ], + [ 'Sommer', 's' ], + ], + 'default' => 'w', + ], + ], + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'ID Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + ] + ], + 'bus_pro_id' => [ + 'exclude' => 0, + 'label' => 'BusProId', + 'config' => [ + 'type' => 'none', + 'format' => 'int', + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'text', + 'cols' => 30, + 'rows' => 2, + 'eval' => 'trim,required' + ], + ], + 'name_internal' => [ + 'exclude' => 0, + 'label' => 'Name intern', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'subline' => [ + 'exclude' => 0, + 'label' => 'Subline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords', + 'description' => 'für Suchschlitz (veraltet)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 2, + 'eval' => 'trim', + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'feature' => [ + 'exclude' => 0, + 'label' => 'Leistungen', + 'description' => 'für Teaser, max 2', + 'config' => [ + 'type' => 'text', + 'cols' => 30, + 'rows' => 2, + 'eval' => 'trim' + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser (Aufzählung)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'teaser_long' => [ + 'exclude' => 0, + 'label' => 'Teaser (Fließtext)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'concept_description' => [ + 'exclude' => 0, + 'label' => 'Konzeptbeschreibung', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'board_description' => [ + 'exclude' => 0, + 'label' => 'Verpflegung', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'additional_services' => [ + 'exclude' => 0, + 'label' => 'Zusatzleistungen', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'included_services' => [ + 'exclude' => 0, + 'label' => 'Inklusivleistungen (Gruppen)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 5, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'programme_description' => [ + 'exclude' => 0, + 'label' => 'Programm', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'ski_pass_alt_title' => [ + 'exclude' => 0, + 'label' => 'Skipass alternative überschrift', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'ski_pass_description' => [ + 'exclude' => 0, + 'label' => 'Skipass', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'rating_average' => [ + 'exclude' => 0, + 'label' => 'Durchschnittliche Bewertung', + 'config' => [ + 'type' => 'input', + 'size' => 5, + 'eval' => 'double2' + ], + ], + 'rating_votes_count' => [ + 'exclude' => 0, + 'label' => 'Abgebene Stimmen', + 'config' => [ + 'type' => 'input', + 'size' => 10, + 'eval' => 'int' + ], + ], + 'testimonials' => [ + 'exclude' => 0, + 'label' => 'Testimonials', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 5, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'Teaser', + 'value' => 2, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header', + 'value' => 3.805, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Slider', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.625, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'reseller_images' => [ + 'exclude' => 0, + 'label' => 'Bilder für Vertriebspartner', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'reseller_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'facebook' => [ + 'title' => 'Facebook', + 'allowedAspectRatios' => [ + 'ad' => [ + 'title' => 'Single product ad', + 'value' => 1.91, + ], + 'carousel' => [ + 'title' => 'Carousel', + 'value' => 1, + ], + ] + ], + 'google' => [ + 'title' => 'Google', + 'allowedAspectRatios' => [ + 'catalog' => [ + 'title' => 'Catalog', + 'value' => 1, + ], + ] + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'tour_thumbnail' => [ + 'exclude' => 0, + 'label' => 'Thumbnail Rundgang', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'tour_thumbnail', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 1, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'concept' => [ + 'exclude' => 0, + 'label' => 'Konzept', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_concept', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minItems' => 1, + 'maxitems' => 1, + ], + ], + 'country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'region' => [ + 'exclude' => 0, + 'label' => 'Gebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'city' => [ + 'exclude' => 0, + 'label' => 'Stadt/Ort', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_city', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'journey' => [ + 'exclude' => 0, + 'label' => 'Anreise', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_journey', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'items' => [ + ['Keine Zuordnung', 0] + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'facts' => [ + 'exclude' => 0, + 'label' => 'Facts', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_fact.name', + 'MM' => 'tx_epproducts_product_fact_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_fact', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + 'hotels' => [ + 'exclude' => 0, + 'label' => 'Unterkünfte', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_hotel.name', + 'MM' => 'tx_epproducts_product_hotel_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'minItems' => 1, + 'maxitems' => 9999, + 'multiple' => 0, + ], + ], + 'calendar_hotel' => [ + 'exclude' => 0, + 'label' => 'Unterkunft für Belegungskalender', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_hotel.name', + 'items' => [ + [ 'Kein Kalender', 0 ], + ], + ], + ], + 'officetip' => [ + 'exclude' => 0, + 'label' => 'Officetipp', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_officetip', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'faqs' => [ + 'exclude' => 0, + 'label' => 'FAQs', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_faq', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_faq.label', + 'MM' => 'tx_epproducts_product_faq_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_faq', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + 'alt_product' => [ + 'exclude' => 0, + 'label' => 'Produkt', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_product.uid != ###THIS_UID### ORDER BY tx_epproducts_domain_model_product.name_internal', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'alt_label' => [ + 'exclude' => 0, + 'label' => 'Button Label', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'video' => [ + 'exclude' => 0, + 'label' => 'Video (Vimeo ID)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'banner' => [ + 'exclude' => 0, + 'label' => 'Banner für Sidebar', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'banner', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'additional_regions' => [ + 'exclude' => 0, + 'label' => 'Weitere Gebiete', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_region.deleted = 0 ORDER BY tx_epproducts_domain_model_region.name', + 'MM' => 'tx_epproducts_product_region_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + ], + ], + 'badge' => [ + 'exclude' => 0, + 'label' => 'Badge', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_badge', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_badge.deleted = 0 ORDER BY tx_epproducts_domain_model_badge.title', + 'items' => [ + ['Keine Auswahl', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'non_bookable_info' => [ + 'exclude' => 0, + 'label' => 'Infobox nicht buchbar', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_infobox', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_infobox.deleted = 0 ORDER BY tx_epproducts_domain_model_infobox.title', + 'items' => [ + ['Standardtext', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'external_link' => [ + 'exclude' => false, + 'label' => 'Externer Link', + 'config' => [ + 'type' => 'input', + 'eval' => 'trim', + ] + ], + 'tour_link' => [ + 'exclude' => 0, + 'label' => 'URL 360° Rundgang (matterport)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'renderType' => 'inputLink', + ], + ], + 'path_segment' => [ + 'exclude' => 0, + 'label' => 'Buchungslink URL', + 'config' => [ + 'type' => 'slug', + 'generatorOptions' => [ + 'fields' => ['name'], + 'prefixParentPageSlug' => false, + 'replacements' => [ + '/' => '-', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + ], + ], + 'non_bookable_dates' => [ + 'exclude' => 0, + 'label' => 'Nicht buchbare Termine', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'itemsProcFunc' => \EP\EpProducts\UserFunc\TcaProcFunc::class.'->getDateItemsForProduct', + ] + ], + 'min_price' => [ + 'exclude' => 0, + 'label' => 'Ab-Preis', + 'config' => [ + 'type' => 'input', + 'eval' => 'int' + ], + ] + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_region.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_region.php new file mode 100644 index 0000000..b2c7cf6 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_region.php @@ -0,0 +1,609 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_region', + 'label' => 'name_internal', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name_internal', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,headline,teaser,ski_area,ski_pass,description,news', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;top, name_internal, --palette--;;name, --palette--;;destinations, + --palette--;;livedata, teaser, keywords, facts, officetip,--palette--;Auszeichnung;award, + --div--;Texte, ski_area, ski_area_extended, ski_pass, description, news, + --div--;Daten, --palette--;;dimensions, + --div--;Bilder/Dateien, teaser_images, header_images, images, region_maps', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'hidden,detail_page,exclude_from_teasergroups'], + 'name' => ['showitem' => 'name,headline,--linebreak--,header_title,header_subtitle'], + 'destinations' => ['showitem' => 'country,season,latitude,longitude'], + 'dimensions' => ['showitem' => 'length,height,lifts'], + 'livedata' => ['showitem' => 'snowreport,webcam,--linebreak--,feature'], + 'award' => ['showitem' => 'award_link, award_label, --linebreak--, award_image'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_region.pid=###CURRENT_PID### AND tx_epproducts_domain_model_region.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'detail_page' => [ + 'exclude' => 0, + 'label' => 'Detailseite', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'renderType' => 'inputLink', + ] + ], + 'exclude_from_teasergroups' => [ + 'exclude' => 0, + 'label' => 'Nicht in Teasergruppen', + 'config' => [ + 'type' => 'check', + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'name_internal' => [ + 'exclude' => 0, + 'label' => 'Name intern', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'season' => [ + 'exclude' => 0, + 'label' => 'Saison', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Winter', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_WINTER], + ['Sommer', \EP\EpProducts\Domain\Model\AbstractDestination::SEASON_SUMMER], + ], + ] + ], + 'keywords' => [ + 'exclude' => 0, + 'label' => 'Keywords für Suchschlitz', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser' => [ + 'exclude' => 0, + 'label' => 'Teaser', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'header_title' => [ + 'exclude' => 0, + 'label' => 'Header Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_subtitle' => [ + 'exclude' => 0, + 'label' => 'Header Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'ski_area' => [ + 'exclude' => 0, + 'label' => 'Das Skigebiet/Highlights (oben)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'ski_area_extended' => [ + 'exclude' => 0, + 'label' => 'Das Skigebiet/Highlights (unten)', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'ski_pass' => [ + 'exclude' => 0, + 'label' => 'Der Skipass', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Anfänger, Snowpark, Freeriden', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'news' => [ + 'exclude' => 0, + 'label' => 'Aktuell', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'length' => [ + 'exclude' => 0, + 'label' => 'Pisten', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'height' => [ + 'exclude' => 0, + 'label' => 'Höhe', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'lifts' => [ + 'exclude' => 0, + 'label' => 'Lifte', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'feature' => [ + 'exclude' => 0, + 'label' => 'Feature für Teaser', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'teaser_images' => [ + 'exclude' => 0, + 'label' => 'Teaserbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'teaser_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'Teaser', + 'value' => 2, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'header_images' => [ + 'exclude' => 0, + 'label' => 'Headerbild(er)', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'headerLarge' => [ + 'title' => 'Header', + 'value' => 3.8, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'images' => [ + 'exclude' => 0, + 'label' => 'Galeriebilder', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'images', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Preview', + 'allowedAspectRatios' => [ + 'preview' => [ + 'title' => 'Standard', + 'value' => 1.8, + ], + ], + ], + 'zoom' => [ + 'title' => 'Zoom', + 'allowedAspectRatios' => [ + 'free' => [ + 'title' => 'frei', + 'value' => 'NaN', + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 10 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'region_maps' => [ + 'exclude' => 0, + 'label' => 'Gebietskarten', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'region_maps', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 5 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'latitude' => [ + 'exclude' => 0, + 'label' => 'Breitengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'longitude' => [ + 'exclude' => 0, + 'label' => 'Längengrad', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ] + ], + 'country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'minitems' => 1, + 'maxitems' => 1, + ], + ], + 'facts' => [ + 'exclude' => 0, + 'label' => 'Facts', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_fact', + 'MM' => 'tx_epproducts_region_fact_mm', + 'size' => 10, + 'autoSizeMax' => 30, + 'maxitems' => 9999, + 'multiple' => 0, + 'fieldControl' => [ + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'table' => 'tx_epproducts_domain_model_fact', + 'pid' => '###PAGE_TSCONFIG_ID###', + 'setValue' => 'prepend', + ], + ], + ], + ], + ], + 'officetip' => [ + 'exclude' => 0, + 'label' => 'Officetip', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_officetip', + 'items' => [ + ['Keine Zuordnung', 0], + ], + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'snowreport' => [ + 'exclude' => 0, + 'label' => 'Gebiet für Schneehöhen', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['Keine Zuordnung', 0] + ], + 'foreign_table' => 'tx_epproducts_domain_model_snowreport', + 'foreign_table_where' => 'ORDER BY tx_epproducts_domain_model_snowreport.region_name', + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'webcam' => [ + 'exclude' => 0, + 'label' => 'Webcam', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'items' => [ + ['Keine Zuordnung', 0] + ], + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_webcam', +// 'foreign_table_where' => 'AND tx_epproducts_domain_model_webcam.available = 1 AND tx_epproducts_domain_model_webcam.snowreport=###REC_FIELD_snowreport### ORDER BY tx_epproducts_domain_model_webcam.name', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_webcam.snowreport=###REC_FIELD_snowreport### ORDER BY tx_epproducts_domain_model_webcam.name', + 'minitems' => 0, + 'maxitems' => 1, + ], + ], + 'award_image' => [ + 'exclude' => 0, + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'award_image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1 + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'award_link' => [ + 'exclude' => 0, + 'label' => 'Link', + 'config' => [ + 'type' => 'input', + 'size' => 32, + 'eval' => 'trim', + 'renderType' => 'inputLink', + ] + ], + 'award_label' => [ + 'exclude' => 0, + 'label' => 'Label', + 'config' => [ + 'type' => 'input', + 'size' => 32, + 'eval' => 'trim', + 'default' => 'Auszeichnung Skigebiet', + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_reseller.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_reseller.php new file mode 100644 index 0000000..eae2dfe --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_reseller.php @@ -0,0 +1,154 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_reseller', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => ['showitem' => 'name, code, pa_code, products'], + ], + 'palettes' => [], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_reseller', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_reseller.pid=###CURRENT_PID### AND tx_epproducts_domain_model_reseller.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'code' => [ + 'exclude' => 0, + 'label' => 'Code', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'pa_code' => [ + 'exclude' => 0, + 'label' => 'PA-Code', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'products' => [ + 'exclude' => 0, + 'label' => 'Produkte', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_epproducts_domain_model_product', + 'MM' => 'tx_epproducts_reseller_product_mm', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_product.name_internal', + 'minitems' => 1, + 'maxitems' => 99, + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_snowreport.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_snowreport.php new file mode 100644 index 0000000..3a09753 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_snowreport.php @@ -0,0 +1,286 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_snowreport', + 'label' => 'region_name', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY region_name', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'region_name', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, vendor_uid, region_name, glacier, height_max, height_min, opened, + snow_level_mountain, snow_level_valley, snow_quality, last_snowfall, lifts_total, lifts_opened, + last_update, season_start, season_end, --div--;Webcams, webcams', + ], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_snowreport', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_snowreport.pid=###CURRENT_PID### AND tx_epproducts_domain_model_snowreport.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'pid' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'vendor_uid' => [ + 'exclude' => 0, + 'label' => 'Schneereport ID', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'region_name' => [ + 'exclude' => 0, + 'label' => 'Skigebiet', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'glacier' => [ + 'exclude' => 0, + 'label' => 'Gletscher', + 'config' => [ + 'type' => 'check', + ], + ], + 'height_max' => [ + 'exclude' => 0, + 'label' => 'Höhe max.', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'height_min' => [ + 'exclude' => 0, + 'label' => 'Höhe min.', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'opened' => [ + 'exclude' => 0, + 'label' => 'Gebiet geöffnet', + 'config' => [ + 'type' => 'check', + ], + ], + 'snow_level_mountain' => [ + 'exclude' => 0, + 'label' => 'Schneehöhe Berg', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'snow_level_valley' => [ + 'exclude' => 0, + 'label' => 'Schneehöhe Tal', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'snow_quality' => [ + 'exclude' => 0, + 'label' => 'Schneequalität', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'last_snowfall' => [ + 'exclude' => 0, + 'label' => 'Letzter Schneefall', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'range' => [ + 'lower' => mktime(0, 0, 0, date('m'), date('d'), date('Y')) + ], + 'renderType' => 'inputDateTime', + ], + ], + 'lifts_total' => [ + 'exclude' => 0, + 'label' => 'Lifte gesamt', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'lifts_opened' => [ + 'exclude' => 0, + 'label' => 'Lifte geöffnet', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'int' + ], + ], + 'last_update' => [ + 'exclude' => 0, + 'label' => 'Letzte Aktualisierung', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'range' => [ + 'lower' => mktime(0, 0, 0, date('m'), date('d'), date('Y')) + ], + 'renderType' => 'inputDateTime', + ], + ], + 'webcams' => [ + 'exclude' => 0, + 'label' => 'Webcams', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_webcam', + 'foreign_field' => 'snowreport', + ], + ], + 'season_start' => [ + 'exclude' => 0, + 'label' => 'Saison Anfang', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'range' => [ + 'lower' => mktime(0, 0, 0, date('m'), date('d'), date('Y')) + ], + 'renderType' => 'inputDateTime', + ], + ], + 'season_end' => [ + 'exclude' => 0, + 'label' => 'Saison Ende', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'range' => [ + 'lower' => mktime(0, 0, 0, date('m'), date('d'), date('Y')) + ], + 'renderType' => 'inputDateTime', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_staticsearchparams.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_staticsearchparams.php new file mode 100644 index 0000000..cb1801c --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_staticsearchparams.php @@ -0,0 +1,258 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_staticsearchparams', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,path_segment', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'name, path_segment, country, region, concept, hotel, product, date_from, + date_to, hotel_types, bus, pax', + ], + ], + 'palettes' => [], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_staticsearchparams', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_staticsearchparams.pid=###CURRENT_PID### AND tx_epproducts_domain_model_staticsearchparams.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_product.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'bus' => [ + 'exclude' => 0, + 'label' => 'Bus', + 'config' => [ + 'type' => 'check', + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'cols' => 30, + 'eval' => 'trim,required' + ], + ], + 'path_segment' => [ + 'exclude' => 0, + 'label' => 'URL Segment', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required,unique,alphanum_x,lower' + ], + ], + 'concept' => [ + 'exclude' => 0, + 'label' => 'Konzept', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingleBox', + 'foreign_table' => 'tx_epproducts_domain_model_concept', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minItems' => 0, + 'maxitems' => 10, + 'size' => 5, + 'items' => [ + [ 'Keine Auswahl', 0 ], + ] + ], + ], + 'country' => [ + 'exclude' => 0, + 'label' => 'Land', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_country', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minitems' => 0, + 'maxitems' => 1, + 'items' => [ + [ 'Keine Auswahl', 0 ], + ] + ], + ], + 'region' => [ + 'exclude' => 0, + 'label' => 'Gebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_region', + 'foreign_table_where' => 'AND 1=1 ORDER BY name', + 'minitems' => 0, + 'maxitems' => 1, + 'items' => [ + [ 'Keine Auswahl', 0 ], + ] + ], + ], + 'hotel' => [ + 'exclude' => 0, + 'label' => 'Hotel', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_epproducts_domain_model_hotel', + 'foreign_table_where' => 'AND 1=1 ORDER BY tx_epproducts_domain_model_hotel.name', + 'minitems' => 0, + 'maxitems' => 1, + 'items' => [ + [ 'Keine Auswahl', 0 ], + ] + ], + ], + 'hotel_types' => [ + 'exclude' => 0, + 'label' => 'Unterkunftsarten', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectCheckBox', + 'items' => [ + [ 'Keine Auswahl', 0 ], + [ 'Standard', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_STANDARD ], + [ 'Komfort', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_COMFORT ], + [ 'Deluxe', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_DELUXE ], + [ 'Superdeluxe', \EP\EpProducts\Domain\Model\Hotel::CATEGORY_SUPER_DELUXE ], + ], + 'minitems' => 0, + 'maxitems' => 99, + ] + ], + 'date_from' => [ + 'exclude' => 0, + 'label' => 'Datum von', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ] + ], + 'date_to' => [ + 'exclude' => 0, + 'label' => 'Datum bis', + 'config' => [ + 'dbType' => 'date', + 'type' => 'input', + 'size' => 7, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => '0000-00-00', + 'renderType' => 'inputDateTime', + ] + ], + 'pax' => [ + 'exclude' => 0, + 'label' => 'Personen', + 'config' => [ + 'type' => 'input', + 'size' => 2, + 'eval' => 'int', + ] + ] + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_teammember.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_teammember.php new file mode 100644 index 0000000..16e9fa4 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_teammember.php @@ -0,0 +1,214 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_teammember', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name', + 'sortby' => 'sorting', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name,email,', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, --palette--;;top, --palette--;;contact, description, image, path_segment', + ], + ], + 'palettes' => [ + 'top' => ['showitem' => 'name,tasks'], + 'contact' => ['showitem' => 'email,phone'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_teammember', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_teammember.pid=###CURRENT_PID### AND tx_epproducts_domain_model_teammember.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + + 'name' => [ + 'exclude' => 0, + 'label' => 'Name', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'path_segment' => [ + 'exclude' => 0, + 'label' => 'Pfadsegment', + 'config' => [ + 'type' => 'slug', + 'generatorOptions' => [ + 'fields' => ['name'], + 'prefixParentPageSlug' => false, + 'replacements' => [ + '/' => '-', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + ], + ], + 'email' => [ + 'exclude' => 0, + 'label' => 'E-Mail', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'phone' => [ + 'exclude' => 0, + 'label' => 'Telefon', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'tasks' => [ + 'exclude' => 0, + 'label' => 'Aufgaben', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 2, + 'eval' => 'trim' + ], + ], + 'description' => [ + 'exclude' => 0, + 'label' => 'Beschreibung', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ], + ], + 'image' => [ + 'exclude' => 0, + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php new file mode 100644 index 0000000..a65bd53 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfo.php @@ -0,0 +1,374 @@ + [ + 'title' => 'Reiseinformationen', + 'default_sortby' => 'ORDER BY title', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'origUid' => 't3_origuid', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,travel_code', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;basics,--div--;Texte,--palette--;;text,--div--;Bilder,--palette--;;images', + ], + ], + 'palettes' => [ + 'basics' => ['showitem' => 'title,path_segment,--linebreak--,travel_code,hide_address,--linebreak--,links, + --linebreak--,teasers'], + 'text' => ['showitem' => 'subline,headline,--linebreak--,intro_text,--linebreak--, bus_info_text,--linebreak--, + departure_text,--linebreak--,footer_text,--linebreak--,self_arranged_text,--linebreak--,additional_info, + --linebreak--,important_phone_numbers,--linebreak--,important_links'], + 'images' => ['showitem' => 'header_image,--linebreak--,footer_image'], + ], + 'columns' => [ + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_travelinfo', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_travelinfo.pid=###CURRENT_PID### AND tx_epproducts_domain_model_travelinfo.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'Titel (nur intern genutzt)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'subline' => [ + 'exclude' => 0, + 'label' => 'Subline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'headline' => [ + 'exclude' => 0, + 'label' => 'Headline', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim' + ], + ], + 'header_image' => [ + 'exclude' => 0, + 'label' => 'Headerbild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'header_image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'footer_image' => [ + 'exclude' => 0, + 'label' => 'Footerbild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'footer_image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'travel_code' => [ + 'exclude' => false, + 'label' => 'Reisecode', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'hide_address' => [ + 'exclude' => 0, + 'label' => 'Adresse ausblenden', + 'config' => [ + 'type' => 'check', + ], + ], + 'intro_text' => [ + 'exclude' => false, + 'label' => 'Introtext', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim,required', + 'enableRichtext' => true, + ] + ], + 'bus_info_text' => [ + 'exclude' => false, + 'label' => 'Businfo', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'departure_text' => [ + 'exclude' => false, + 'label' => 'Abreise', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'footer_text' => [ + 'exclude' => false, + 'label' => 'Footertext', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 3, + 'eval' => 'trim,required', + 'enableRichtext' => true, + ] + ], + 'self_arranged_text' => [ + 'exclude' => false, + 'label' => 'Eigenanreise', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'additional_info' => [ + 'exclude' => false, + 'label' => 'Weitere Infos', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'important_phone_numbers' => [ + 'exclude' => false, + 'label' => 'Wichtige Telefonnummern', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'important_links' => [ + 'exclude' => false, + 'label' => 'Wichtige Links', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + 'enableRichtext' => true, + ] + ], + 'teasers' => [ + 'exclude' => false, + 'label' => 'Inhalte', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_travelinfoteaser', + 'foreign_sortby' => 'sorting', + 'foreign_field' => 'tx_epproducts_travelinfo', + 'minitems' => 0, + 'maxitems' => 99, + 'appearance' => [ + 'collapseAll' => true, + 'expandSingle' => true, + 'levelLinksPosition' => 'bottom', + 'useSortable' => true, + 'showPossibleLocalizationRecords' => false, + 'showRemovedLocalizationRecords' => false, + 'showAllLocalizationLink' => false, + 'showSynchronizationLink' => false, + 'enabledControls' => [ + 'info' => false, + ], + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => false, + ], + ] + ], + 'links' => [ + 'exclude' => false, + 'label' => 'Links', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_epproducts_domain_model_travelinfolink', + 'foreign_sortby' => 'sorting', + 'foreign_field' => 'tx_epproducts_travelinfo', + 'minitems' => 0, + 'maxitems' => 3, + 'appearance' => [ + 'collapseAll' => true, + 'expandSingle' => true, + 'levelLinksPosition' => 'bottom', + 'useSortable' => true, + 'showPossibleLocalizationRecords' => false, + 'showRemovedLocalizationRecords' => false, + 'showAllLocalizationLink' => false, + 'showSynchronizationLink' => false, + 'enabledControls' => [ + 'info' => false, + ], + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => false, + ], + ] + ], + 'path_segment' => [ + 'exclude' => 0, + 'label' => 'URL', + 'config' => [ + 'type' => 'slug', + 'generatorOptions' => [ + 'fields' => ['title'], + 'prefixParentPageSlug' => false, + 'replacements' => [ + '/' => '-', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfolink.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfolink.php new file mode 100644 index 0000000..552ed74 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfolink.php @@ -0,0 +1,167 @@ + [ + 'title' => 'Reiseinfo Link', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;basics,link', + ], + ], + 'palettes' => [ + 'basics' => ['showitem' => 'title,icon,hidden'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_travelinfolink', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_travelinfolink.pid=###CURRENT_PID### AND tx_epproducts_domain_model_travelinfolink.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'link' => [ + 'exclude' => false, + 'label' => 'Link', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputLink', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'icon' => [ + 'exclude' => 0, + 'label' => 'Icon', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Anreise', 'travel'], + ['Skipass', 'skipass'], + ['Kurse', 'course'], + ['Verpflegung', 'food'], + ['Gepäck', 'luggage'], + ['Lift', 'lift'], + ['Webcam', 'webcam'], + ['Wetter', 'weather'], + ['Wissenswertes', 'chat'], + ['Währung', 'currency'], + ], + ], + ], + 'tx_epproducts_travelinfo' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfoteaser.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfoteaser.php new file mode 100644 index 0000000..d22d0d7 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_travelinfoteaser.php @@ -0,0 +1,220 @@ + [ + 'title' => 'Reiseinfo Teaser', + 'label' => 'title', + 'prependAtCopy' => '[COPY %s]', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'origUid' => 't3_origuid', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'hideTable' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => '--palette--;;basics,bodytext,image', + ], + ], + 'palettes' => [ + 'basics' => ['showitem' => 'title,category,hidden'], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_travelinfoteaser', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_travelinfoteaser.pid=###CURRENT_PID### AND tx_epproducts_domain_model_travelinfoteaser.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'Bezeichnung/Überschrift', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'bodytext' => [ + 'exclude' => false, + 'label' => 'Text', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim,required', + 'enableRichtext' => true, + ] + ], + 'image' => [ + 'exclude' => 0, + 'label' => 'Bild', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'image', + [ + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference' + ], + 'overrideChildTca' => [ + 'types' => [ + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + 'columns' => [ + 'crop' => [ + 'config' => [ + 'cropVariants' => [ + 'default' => [ + 'title' => 'Alle Geräte', + 'allowedAspectRatios' => [ + 'teaser' => [ + 'title' => 'quadtratisch', + 'value' => 1, + ], + ], + ], + ], + ], + ], + ], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], + 'category' => [ + 'exclude' => 0, + 'label' => 'Kategorie/Icon', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['Anreise', 'travel'], + ['Auto', 'car'], + ['Bus', 'bus'], + ['Gepäck', 'luggage'], + ['Getränke', 'drinks'], + ['Kurse', 'course'], + ['Lift', 'lift'], + ['Programm', 'program'], + ['Skipass', 'skipass'], + ['Verpflegung', 'food'], + ['Webcam', 'webcam'], + ['Wetter', 'weather'], + ['Wissenswertes', 'chat'], + ['Leihmaterial', 'rental'], + ['Teilnehmer', 'participants'], + ['Bett', 'bed'], + ['Ablauf', 'sequence'], + ['Partner', 'partner'], + ], + 'maxitems' => 1, + 'minitems' => 1, + ], + ], + 'tx_epproducts_travelinfo' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + 'sorting' => [ + 'config' => [ + 'type' => 'passthrough' + ] + ], + ], +]; diff --git a/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_webcam.php b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_webcam.php new file mode 100644 index 0000000..777bb74 --- /dev/null +++ b/packages/ep_products/Configuration/TCA/tx_epproducts_domain_model_webcam.php @@ -0,0 +1,164 @@ + [ + 'title' => 'LLL:EXT:ep_products/Resources/Private/Language/locallang.xlf:tx_epproducts_domain_model_webcam', + 'label' => 'name', + 'prependAtCopy' => '[COPY %s]', + 'default_sortby' => 'ORDER BY name', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'dividers2tabs' => true, + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'name', + 'iconfile' => 'EXT:ep_theme/Resources/Public/images/icon_ce.svg', + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, name, url, vendor_uid, available, snowreport', + ], + ], + 'columns' => [ + + 'sys_language_uid' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1], + ['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0] + ], + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingle', + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_epproducts_domain_model_webcam', + 'foreign_table_where' => 'AND tx_epproducts_domain_model_webcam.pid=###CURRENT_PID### AND tx_epproducts_domain_model_webcam.sys_language_uid IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + + 't3ver_label' => [ + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.versionLabel', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'max' => 255, + ] + ], + + 'hidden' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'starttime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'datetime', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + 'endtime' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'size' => 13, + 'eval' => 'date', + 'checkbox' => 0, + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'renderType' => 'inputDateTime', + ], + ], + + 'pid' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'name' => [ + 'exclude' => 0, + 'label' => 'Bezeichnung', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'url' => [ + 'exclude' => 0, + 'label' => 'URL', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'vendor_uid' => [ + 'exclude' => 0, + 'label' => 'ID', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim,required' + ], + ], + 'available' => [ + 'exclude' => 0, + 'label' => 'verfügbar', + 'config' => [ + 'type' => 'check', + ], + ], + 'snowreport' => [ + 'exclude' => 0, + 'label' => 'Skigebiet', + 'config' => [ + 'type' => 'select', + 'default' => 0, + 'renderType' => 'selectSingleBox', + 'foreign_table' => 'tx_epproducts_domain_model_snowreport', + ], + ], + ], +]; diff --git a/packages/ep_products/Configuration/TypoScript/constants.typoscript b/packages/ep_products/Configuration/TypoScript/constants.typoscript new file mode 100644 index 0000000..6d6c778 --- /dev/null +++ b/packages/ep_products/Configuration/TypoScript/constants.typoscript @@ -0,0 +1,68 @@ +# customsubcategory=100=Storage +# customsubcategory=110=Pages +# customsubcategory=120=Search/Filtering +# customsubcategory=130=Email +# customsubcategory=140=Misc + +plugin.tx_epproducts { + settings { + # cat=epproducts/100/100; type=string; label=Products storage PID + productsStoragePid = + # cat=epproducts/100/110; type=string; label=Hotels storage PID + hotelsStoragePid = + # cat=epproducts/100/120; type=string; label=Concepts storage PID + conceptsStoragePid = + # cat=epproducts/100/130; type=string; label=Facts storage PID + factsStoragePid = + # cat=epproducts/100/140; type=string; label=Faqs storage PID + faqsStoragePid = + # cat=epproducts/100/150; type=string; label=Destinations storage PID + destinationsStoragePid = + # cat=epproducts/100/160; type=string; label=Regions storage PID + regionsStoragePid = + # cat=epproducts/100/160; type=string; label=Cities storage PID + citiesStoragePid = + # cat=epproducts/100/170; type=string; label=Teammember storage PID + teammemberStoragePid = + # cat=epproducts/100/180; type=string; label=Officetip storage PID + officetipStoragePid = + # cat=epproducts/100/190; type=string; label=Snow report storage PID + snowReportStoragePid = + # cat=epproducts/110/100; type=string; label=Default search page UID + defaultSearchPageUid = + # cat=epproducts/110/110; type=string; label=Default product detail page UID + defaultDetailPageUid = + # cat=epproducts/110/120; type=string; label=Page UID with reseller export plugin + resellerExportPageUid = + # cat=epproducts/110/130; type=string; label=Page UID with travel info plugin + travelInfoPageUid = + # cat=epproducts/120/100; type=string; label=Exclude products with concept UIDs from search result (CSV) + excludedConceptUids = + # cat=epproducts/120/110; type=string; label=Exclude regions with these UIDs from filter options (CSV) + excludedRegionUids = + # cat=epproducts/120/120; type=string; label=Force concept UID in all search related parts (CSV) + forcedConceptUids = + # cat=epproducts/120/130; type=string; label=Don't show availability info for products with these concept UIDs + noInfoConceptUids = + # cat=epproducts/130/110; type=string; label=Email recipient for groups price inquiries + groupsPriceToEmail = gruppen@ep-reisen.de + # cat=epproducts/130/120; type=string; label=Email recipient for groups price inquiries + groupsPriceToName = EP Reisen + # cat=epproducts/130/130; type=string; label=Email recipient for groups price inquiries + groupsBookingUrl = https://myep.ddev.site/groups/booking/init + # cat=epproducts/140/100; type=string; label=Room types for pricetables (CSV) + priceTableRoomTypes = 1,2,3,4,5,6,7,8,9,10 + # cat=epproducts/140/110; type=string; label=Ignore following IPs in searchlog + searchLogIgnoreIps = + # cat=epproducts/140/120; type=string; label=Concept UIDs to trigger inquiry form on product detail page (CSV) + groupInquiryFormConceptUids = + # cat=epproducts/140/130; type=string; label=Per-site MyE&P booking URL override + myEpUrl = + # cat=epproducts/140/140; type=string; label=Per-site MyE&P staging booking URL override + myEpStagingUrl = + } + persistence { + # cat=epproducts/100/100; type=string; label=Default storage PID + storagePid = + } +} diff --git a/packages/ep_products/Configuration/TypoScript/setup.typoscript b/packages/ep_products/Configuration/TypoScript/setup.typoscript new file mode 100644 index 0000000..a5378bb --- /dev/null +++ b/packages/ep_products/Configuration/TypoScript/setup.typoscript @@ -0,0 +1,303 @@ +plugin.tx_epproducts { + view { + layoutRootPaths.0 = EXT:ep_theme/Resources/Private/Layouts/Product/ + templateRootPaths.0 = EXT:ep_theme/Resources/Private/Templates/ + partialRootPaths.0 = EXT:ep_theme/Resources/Private/Partials/ + } + persistence { + storagePid = {$plugin.tx_epproducts.persistence.storagePid} + classes { + EP\EpProducts\Domain\Model\Product.newRecordStoragePid = {$plugin.tx_epproducts.settings.productsStoragePid} + EP\EpProducts\Domain\Model\Hotel.newRecordStoragePid = {$plugin.tx_epproducts.settings.hotelsStoragePid} + EP\EpProducts\Domain\Model\Country.newRecordStoragePid = {$plugin.tx_epproducts.settings.destinationsStoragePid} + EP\EpProducts\Domain\Model\Region.newRecordStoragePid = {$plugin.tx_epproducts.settings.destinationsStoragePid} + EP\EpProducts\Domain\Model\City.newRecordStoragePid = {$plugin.tx_epproducts.settings.destinationsStoragePid} + EP\EpProducts\Domain\Model\Journey.newRecordStoragePid = {$plugin.tx_epproducts.settings.destinationsStoragePid} + EP\EpProducts\Domain\Model\Concept.newRecordStoragePid = {$plugin.tx_epproducts.settings.conceptsStoragePid} + EP\EpProducts\Domain\Model\Fact.newRecordStoragePid = {$plugin.tx_epproducts.settings.factsStoragePid} + EP\EpProducts\Domain\Model\Faq.newRecordStoragePid = {$plugin.tx_epproducts.settings.faqsStoragePid} + EP\EpProducts\Domain\Model\Teammember.newRecordStoragePid = {$plugin.tx_epproducts.settings.teammemberStoragePid} + EP\EpProducts\Domain\Model\Officetip.newRecordStoragePid = {$plugin.tx_epproducts.settings.officetipStoragePid} + EP\EpProducts\Domain\Model\Room.newRecordStoragePid = {$plugin.tx_epproducts.persistence.storagePid} + EP\EpProducts\Domain\Model\Date.newRecordStoragePid = {$plugin.tx_epproducts.persistence.storagePid} + EP\EpProducts\Domain\Model\MatchCode.newRecordStoragePid = {$plugin.tx_epproducts.persistence.storagePid} + } + } + settings { + productsPid = {$plugin.tx_epproducts.settings.productsStoragePid} + hotelsPid = {$plugin.tx_epproducts.settings.hotelsStoragePid} + destinationsStoragePid = {$plugin.tx_epproducts.settings.destinationsStoragePid} + regionsStoragePid = {$plugin.tx_epproducts.settings.regionsStoragePid} + citiesStoragePid = {$plugin.tx_epproducts.settings.citiesStoragePid} + snowReportStoragePid = {$plugin.tx_epproducts.settings.snowReportStoragePid} + defaultSearchPageUid = {$plugin.tx_epproducts.settings.defaultSearchPageUid} + defaultDetailPageUid = {$plugin.tx_epproducts.settings.defaultDetailPageUid} + excludedConceptUids = {$plugin.tx_epproducts.settings.excludedConceptUids} + excludedRegionUids = {$plugin.tx_epproducts.settings.excludedRegionUids} + forcedConceptUids = {$plugin.tx_epproducts.settings.forcedConceptUids} + noInfoConceptUids = {$plugin.tx_epproducts.settings.noInfoConceptUids} + priceTableRoomTypes = {$plugin.tx_epproducts.settings.priceTableRoomTypes} + searchLogIgnoreIps = {$plugin.tx_epproducts.settings.searchLogIgnoreIps} + myEpUrl = {$plugin.tx_epproducts.settings.myEpUrl} + myEpStagingUrl = {$plugin.tx_epproducts.settings.myEpStagingUrl} + groupsPageUid = {$plugin.tx_eptheme.settings.groupsPageUid} + travelInfoPageUid = {$plugin.tx_eptheme.settings.travelInfoPageUid} + groupsContactFormPageUid = {$plugin.tx_eptheme.settings.groupsContactFormPageUid} + coronaUpdatesPageUid = {$plugin.tx_eptheme.settings.coronaUpdatesPageUid} + googleMapsApiKey = {$plugin.tx_eptheme.settings.googleMapsApiKey} + resellerExportPageUid = {$plugin.tx_epproducts.settings.resellerExportPageUid} + ratingPageUid = {$plugin.tx_eptheme.settings.ratingPageUid} + travelAlertPageUid = {$plugin.tx_eptheme.settings.travelAlertPageUid} + searchPageHeaderImage = {$plugin.tx_eptheme.settings.searchPageHeaderImage} + bpnBookingUrlTemplateCode = {$plugin.tx_eptheme.settings.bpnBookingUrlTemplateCode} + groupsPricePopupPageUid = {$plugin.tx_eptheme.settings.groupsPricePopupPageUid} + groupsPriceToEmail = {$plugin.tx_epproducts.settings.groupsPriceToEmail} + groupsPriceToName = {$plugin.tx_epproducts.settings.groupsPriceToName} + groupInquiryFormConceptUids = {$plugin.tx_epproducts.settings.groupInquiryFormConceptUids} + groupsBookingUrl = {$plugin.tx_epproducts.settings.groupsBookingUrl} + logoImage = {$plugin.tx_eptheme.settings.logoImage} + themekey = {$plugin.tx_eptheme.settings.themekey} + globalConceptCode = {$plugin.tx_eptheme.settings.globalConceptCode} + dataProtectionPageUid = {$plugin.tx_eptheme.settings.dataProtectionPageUid} + datePickerPresets { + 1 { + label = Silvester + dateFrom = 2017-12-25 + dateTo = 2018-01-06 + } + 2 { + label = Karneval + dateFrom = 2018-02-03 + dateTo = 2018-02-17 + } + 3 { + label = Ostern + dateFrom = 2018-03-24 + dateTo = 2018-04-05 + } + } + groupsSwissApiKeys { + # Spinabad + 7 { + hotelCode = DGS + number = 9849 + key = a8b504ab2784446cbf73b8b1f6777f43 + } + # Waldschlössli + 6 { + hotelCode = DPW + number = 9658 + key = f757b34dc3ac4a158257cf12c9ce69e1 + } + # Jenatsch + 37 { + hotelCode = LPJNEU + number = 9703 + key = 52c314a88a434b359717115db992d20d + } + # Schweizerhaus + 5 { + hotelCode = DKS + number = 9850 + key = 4a955b23158d40c583b2892a18aa1043 + } + # Klein Tirol + 65 { + hotelCode = MVK + number = 668904 + key = e9d86dbc465848d7a0b68df14e3e482f + } + # Weissfluh + 131 { + hotelCode = DWW21/22 + number = 961523 + key = 6a22585d23184f2090bd7110aa9b60f3 + } + } + } +} + +config.tx_extbase.persistence.classes { + EP\EpProducts\Domain\Model\Log.mapping { + tableName = tx_epproducts_log + columns { + data.mapOnProperty = data + time_micro.mapOnProperty = timeMicro + } + } +} + +module.tx_epproducts { + settings < plugin.tx_epproducts.settings + persistence < plugin.tx_epproducts.persistence +} + +# Register Ajax page types (json and html output) +tx_epproducts_ajax_json = PAGE +tx_epproducts_ajax_json { + typeNum = 1701 + config { + disableAllHeaderCode = 1 + disablePrefixComment = 1 + additionalHeaders.10.header = Content-type: application/json + additionalHeaders.20.header = X-Robots-Tag: noindex, nofollow, noarchive + xhtml_cleaning = 0 + admPanel = 0 + debug = 0 + + # The calendar actions are cacheable, but they render volatile availability data. + # TSFE would otherwise fall back to cacheTimeOutDefault (86400), which would widen + # staleness from one hour to a full day. Keep this aligned with the freshness budget. + cache_period = 3600 + # priceCalendarAction renders relative to 'today', so an entry must not outlive the + # day it was rendered on. Near midnight this clips the lifetime below cache_period. + cache_clearAtMidnight = 1 + + # The server-side page cache can be flushed; a max-age already sitting in a visitor's + # browser cannot. Availability is volatile enough that we keep the speed win on the + # server and make clients revalidate every time - they still hit a warm page cache. + # sendCacheHeaders is off so TSFE does not also emit a contradicting Expires/Pragma. + sendCacheHeaders = 0 + additionalHeaders.30.header = Cache-Control: private, no-cache, must-revalidate + + # Same reasoning: staticfilecache serves from disk without touching PHP, which is the + # layer with the least predictable invalidation. Not worth it for volatile fragments. + tx_staticfilecache.disableCache = 1 + } + + 10 = USER + 10 { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = ajax + vendorName = EP + controller = AjaxSearch + action = searchresult + switchableControllerActions { + AjaxSearch { + 1 = pricetable + 2 = searchresult + } + AjaxFilterPanel { + 1 = index + } + AjaxDate { + 1 = dates + } + AjaxTable { + 1 = datestable + 2 = pricetable + 3 = pricetableHtml + 4 = eventPricetable + 5 = daytripRooms + } + AjaxCalendar { + 1 = range + 2 = priceConfig + 3 = priceTable + 4 = priceCalendar + } + AjaxContingent { + 1 = list + 2 = rooms + 3 = ical + } + AjaxWatchlist { + 1 = list + } + AjaxGroupsPrice { + 1 = processForm + } + AjaxTeaserPopup { + 1 = concept + } + } + settings =< plugin.tx_epproducts.settings + persistence =< plugin.tx_epproducts.persistence + view =< plugin.tx_epproducts.view + } +} + +tx_epproducts_ajax_html < tx_epproducts_ajax_json +tx_epproducts_ajax_html.typeNum = 1702 +tx_epproducts_ajax_html.config.additionalHeaders.10.header = Content-type:text/html + +tx_epproducts_ical = PAGE +tx_epproducts_ical { + typeNum = 1710 + config { + disableAllHeaderCode = 1 + disablePrefixComment = 1 + additionalHeaders.10.header = Content-type: text/calendar + xhtml_cleaning = 0 + admPanel = 0 + debug = 0 + } + + 10 = USER + 10 { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = ical + vendorName = EP + controller = Ical + action = contingents + switchableControllerActions { + Ical { + 1 = contingents + } + } + } +} + +lib.bookingWidget = USER +lib.bookingWidget { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = booking + vendorName = EP + controller = Product + action = booking + switchableControllerActions { + Product { + 1 = booking + } + } + settings =< plugin.tx_epproducts.settings + view =< plugin.tx_epproducts.view +} + +# API endpoint for product details (cacheable) +tx_epproducts_api_json = PAGE +tx_epproducts_api_json { + typeNum = 1720 + config { + disableAllHeaderCode = 1 + disablePrefixComment = 1 + additionalHeaders.10.header = Content-type: application/json + additionalHeaders.20.header = Access-Control-Allow-Origin: * + xhtml_cleaning = 0 + admPanel = 0 + debug = 0 + no_cache = 0 + sendCacheHeaders = 1 + } + + 10 = USER + 10 { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = api + vendorName = EP + controller = Api + action = product + switchableControllerActions { + Api { + 1 = product + 2 = hotel + } + } + settings =< plugin.tx_epproducts.settings + persistence =< plugin.tx_epproducts.persistence + } +} diff --git a/packages/ep_products/README.md b/packages/ep_products/README.md new file mode 100644 index 0000000..714800b --- /dev/null +++ b/packages/ep_products/README.md @@ -0,0 +1,17 @@ +Queries for plugin renaming +=========================== + +```mysql +update tt_content set list_type='epproducts_searchbar' where list_type='epproducts_pi1'; +update tt_content set list_type='epproducts_product_detail' where list_type='epproducts_pi3'; +update tt_content set list_type='epproducts_searchresult' where list_type='epproducts_pi2'; +update tt_content set list_type='epproducts_product_teasergroup' where list_type='epproducts_pi4'; +update tt_content set list_type='epproducts_country_detail' where list_type='epproducts_pi5'; +update tt_content set list_type='epproducts_region_teasergroup' where list_type='epproducts_pi6'; +update tt_content set list_type='epproducts_region_detail' where list_type='epproducts_pi10'; +update tt_content set list_type='epproducts_city_teasergroup' where list_type='epproducts_pi7'; +update tt_content set list_type='epproducts_city_detail' where list_type='epproducts_pi11'; +update tt_content set list_type='epproducts_hotel_teasergroup' where list_type='epproducts_pi8'; +update tt_content set list_type='epproducts_hotel_detail' where list_type='epproducts_pi12'; +update tt_content set list_type='epproducts_faq' where list_type='epproducts_pi9'; +``` diff --git a/packages/ep_products/Resources/Private/.htaccess b/packages/ep_products/Resources/Private/.htaccess new file mode 100644 index 0000000..96d0729 --- /dev/null +++ b/packages/ep_products/Resources/Private/.htaccess @@ -0,0 +1,11 @@ +# Apache < 2.3 + + Order allow,deny + Deny from all + Satisfy All + + +# Apache >= 2.3 + + Require all denied + diff --git a/packages/ep_products/Resources/Private/Language/locallang.xlf b/packages/ep_products/Resources/Private/Language/locallang.xlf new file mode 100644 index 0000000..32cf553 --- /dev/null +++ b/packages/ep_products/Resources/Private/Language/locallang.xlf @@ -0,0 +1,68 @@ + + + +
+ + + Termin + + + Produkt + + + Konzept + + + Land + + + Gebiet + + + Stadt/Ort + + + Fact + + + Hotel + + + Faq + + + Code + + + Office-Tipp + + + Mitarbeiter/Teamer + + + Anreise + + + Schneereport + + + Webcam + + + Kontingent + + + Badge + + + Statisches Suchergebnis + + + Vertriebspartner + + + Infobox + + + + diff --git a/packages/ep_products/Resources/Private/Language/locallang_mod.xlf b/packages/ep_products/Resources/Private/Language/locallang_mod.xlf new file mode 100644 index 0000000..1c09931 --- /dev/null +++ b/packages/ep_products/Resources/Private/Language/locallang_mod.xlf @@ -0,0 +1,17 @@ + + + +
+ + + E&P Funktionen + + + Produktspezifische Funktionen + + + Produkte + + + + diff --git a/packages/ep_products/Resources/Private/Layouts/Backend.html b/packages/ep_products/Resources/Private/Layouts/Backend.html new file mode 100644 index 0000000..a606ca0 --- /dev/null +++ b/packages/ep_products/Resources/Private/Layouts/Backend.html @@ -0,0 +1 @@ + diff --git a/packages/ep_products/Resources/Private/Templates/Backend/Index.html b/packages/ep_products/Resources/Private/Templates/Backend/Index.html new file mode 100644 index 0000000..a632d29 --- /dev/null +++ b/packages/ep_products/Resources/Private/Templates/Backend/Index.html @@ -0,0 +1,47 @@ + + + + + +

E&P Funktionen

+ +
+
+

BusPro Daten

+
+
+

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

+

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

+

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

+
+
+

+ + + Schneehöhen importieren + +

+

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

+

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

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

|

+ + # Page templates without a sidebar render colPos 0; the sidebar layouts render 3 (header), + # 4 (content) and 2 (sidebar). colPos 1 holds teaser groups and is left out. The page + # templates pass colPos in as data; here there is no template, so each copy sets it. + # These must be copies (<), not references (=<): removing colPos.cObject on a reference + # leaves the referenced cObject in place, and every column silently renders colPos 0. + 20 < lib.contentOnly + 20.5.colPos.cObject > + 20.5.colPos = 3 + + 30 < lib.contentOnly + 30.5.colPos.cObject > + 30.5.colPos = 0 + + 40 < lib.contentOnly + 40.5.colPos.cObject > + 40.5.colPos = 4 + + 50 < lib.contentOnly + 50.5.colPos.cObject > + 50.5.colPos = 2 + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Page/Export.typoscript b/packages/ep_theme/Configuration/TypoScript/Page/Export.typoscript new file mode 100644 index 0000000..333c2a3 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Page/Export.typoscript @@ -0,0 +1,25 @@ +xmlExport = PAGE +xmlExport { + typeNum = 1972 + config { + xhtml_cleaning = none + disableAllHeaderCode = 1 + additionalHeaders = Content-Type: text/xml; charset=utf-8 + simulateStaticDocuments = 0 + admPanel = 0 + debug = 0 + tx_realurl_enable = 1 + } + 10 = USER_INT + 10 { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + extensionName = EpProducts + pluginName = reseller + vendorName = EP + switchableControllerActions { + Reseller { + 1 = xml + } + } + } +} diff --git a/packages/ep_theme/Configuration/TypoScript/Page/Main.typoscript b/packages/ep_theme/Configuration/TypoScript/Page/Main.typoscript new file mode 100644 index 0000000..42aacca --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/Page/Main.typoscript @@ -0,0 +1,214 @@ +page = PAGE +page { + meta { + viewport = width=device-width, initial-scale=1 + X-UA-Compatible = ie=edge + format-detection = telephone=no + } + + bodyTagCObject = COA + bodyTagCObject { + 10 = TEXT + 10.value = + } + + # Every image on the page comes from the CDN host (see Extensions/Replacer.typoscript), + # so a cold visitor pays a full DNS + TCP + TLS handshake - measured at 50-70ms - before + # the first one can start downloading. `preconnect` gets that out of the way while the + # HTML is still parsing. No `crossorigin`: images are fetched without CORS, and an + # anonymous-vs-CORS mismatch would open a second connection instead of reusing this one. + # Fonts, which would need it, are served by this host now. + headerData.100 = TEXT + headerData.100.value = + + headerData.333 = TEXT + headerData.333.value ( + + + + + + + + + + + + + + + + + + + + + + + ) + + # disableCompression keeps ResourceCompressor's hands off the Encore entries: Webpack + # already minifies them, and copying them to typo3temp/assets/compressed only replaces the + # content hash with a name derived from md5(abspath + mtime + size), which changes on every + # deploy even when the bytes do not - defeating the immutable caching the hash exists for. + # RequestHandler turns this into `compress => false`, which PageRendererHooks forwards to + # typo3_encore's TagRenderer (whose own default it would otherwise override). + includeCSS { + main = typo3_encore:EpThemeConfig:main + main.disableCompression = 1 + } + + includeJSFooter { + main = typo3_encore:EpThemeConfig:main + main.defer = 1 + main.disableCompression = 1 + } + + 10 = FLUIDTEMPLATE + 10 { + file { + cObject = TEXT + cObject { + data = levelfield: -1, backend_layout_next_level, slide + override { + data = TSFE:page|backend_layout + } + + ifEmpty = Default + split { + token = pagets__ + 1.current = 1 + 1.wrap = | + } + + wrap = typo3conf/ext/ep_theme/Resources/Private/Templates/Page/|.html + case = ucfirst + } + } + + layoutRootPaths.0 = EXT:ep_theme/Resources/Private/Layouts/ + partialRootPaths.0 = EXT:ep_theme/Resources/Private/Partials/ + templateRootPaths.0 = EXT:ep_theme/Resources/Private/Templates/ + + dataProcessing { + 10 = EP\EpTheme\DataProcessing\ContextProcessor + 20 = EP\EpTheme\DataProcessing\SliderProcessor + 25 = EP\EpTheme\DataProcessing\ProductRatingProcessor + 30 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor + 30 { + table = tx_epproducts_domain_model_badge + where.data = field:tx_eptheme_badge + where.wrap = uid=| + pidInList = {$plugin.tx_eptheme.settings.badgeStoragePid} + as = badge + if { + equals.field = backend_layout + value = pagets__detail + negate = 1 + } + } + 40 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor + 40 { + special = directory + special.value = {$plugin.tx_eptheme.settings.topNavPid} + levels = 1 + as = topnav + } + 50 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor + 50 { + special = directory + special.value = {$plugin.tx_eptheme.settings.mainNavPid} + levels = 3 + titleField = title + as = mainnav + } + 60 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor + 60 { + special = directory + special.value = {$plugin.tx_eptheme.settings.mobileNavPid} + levels = 4 + expandAll = 1 + titleField = title + as = mobilenav + } + 70 = TYPO3\CMS\Frontend\DataProcessing\MenuProcessor + 70 { + special = directory + special.value = {$plugin.tx_eptheme.settings.footerNavPid} + levels = 2 + expandAll = 1 + titleField = title + as = footernav + } + 80 = TYPO3\CMS\Frontend\DataProcessing\SiteProcessor + 80 { + as = site + } + } + settings < plugin.tx_eptheme.settings + } + + headerData.990 = TEXT + headerData.990 { + value = + if.isTrue = {$plugin.tx_eptheme.settings.cookieFirstScriptUrl} + } + + headerData.995 = TEXT + headerData.995 { + value ( + + + + ) + if.isTrue = {$plugin.tx_eptheme.settings.googleTagManagerId} + } +} + +[page["backend_layout"] == 'pagets__popup'] + page.bodyTagCObject > + page.bodyTagCObject = TEXT + page.bodyTagCObject.value = + page.headerData.333 > +# page.headerData.990 > +# page.headerData.995 > +[end] + +[page["backend_layout"] == 'pagets__travelinfo'] + page.bodyTagCObject > + page.bodyTagCObject = TEXT + page.bodyTagCObject.value = +[end] + +[request.getNormalizedParams() && like(request.getNormalizedParams().getHttpUserAgent(), "*Googlebot*")] + page.headerData.990 > + page.headerData.995 > +[end] + +#[applicationContext == "Development" || applicationContext == "Production/Staging"] +# page.headerData.990 > +# page.headerData.995 > +#[end] diff --git a/packages/ep_theme/Configuration/TypoScript/constants.typoscript b/packages/ep_theme/Configuration/TypoScript/constants.typoscript new file mode 100644 index 0000000..db6d116 --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/constants.typoscript @@ -0,0 +1,202 @@ +# customsubcategory=100=Social links +# customsubcategory=200=Navigations and page UIDs +# customsubcategory=300=Google related +# customsubcategory=400=Contact data +# customsubcategory=500=Template +# customsubcategory=600=XML Sitemap +# customsubcategory=700=Event dates +# customsubcategory=800=Badges +# customsubcategory=900=MyE&P config + +plugin.tx_eptheme { + settings { + # cat=eptheme/200/10; type=string; label=Theme key + themekey = ep + # cat=eptheme/200/100; type=string; label=Top navigation PID + topNavPid = + # cat=eptheme/200/110; type=string; label=Main navigation PID + mainNavPid = + # cat=eptheme/200/115; type=string; label=Mobile navigation PID + mobileNavPid = + # cat=eptheme/200/120; type=string; label=Footer navigation PID + footerNavPid = + # cat=eptheme/200/130; type=string; label=Default homepage UID + defaultHomeUid = + # cat=eptheme/200/131; type=string; label=Main site PID + mainSitePid = + # cat=eptheme/200/135; type=string; label=Link to apply to logo (overrides defaultHomeUid) + logoLink = + # cat=eptheme/200/140; type=string; label=Default page UID for ajax requests + defaultAjaxUid = + # cat=eptheme/200/150; type=string; label=Default contact form page UID + defaultContactFormPageUid = + # cat=eptheme/200/160; type=string; label=Legal notice page UID + legalNoticePageUid = + # cat=eptheme/200/170; type=string; label=Terms and conditions page UID + termsAndConditionsPageUid = + # cat=eptheme/200/180; type=string; label=Data protection page UID + dataProtectionPageUid = + # cat=eptheme/200/185; type=string; label=Image credits page UID + imageCreditsPageUid = + # cat=eptheme/200/190; type=string; label=Cookie policy page UID + cookiePolicyPageUid = + # cat=eptheme/200/200; type=string; label=FAQ page UID + faqPageUid = + # cat=eptheme/200/210; type=string; label=Info page UID + infoPageUid = + # cat=eptheme/200/220; type=string; label=Groups page UID + groupsPageUid = + # cat=eptheme/200/225; type=string; label=Blog detail page UID + blogDetailPageUid = + # cat=eptheme/200/230; type=string; label=Blog tag list page UID + blogTagListPageUid = + # cat=eptheme/200/240; type=string; label=Groups contact form page UID + groupsContactFormPageUid = + # cat=eptheme/200/250; type=string; label=Rating page UID + ratingPageUid = + # cat=eptheme/200/260; type=string; label=Watchlist page UID + watchlistPageUid = + # cat=eptheme/200/270; type=string; label=Skipass info page UID + skipassPageUid = + # cat=eptheme/200/280; type=string; label=English page UID + englishPageUid = + # cat=eptheme/200/285; type=string; label=German page UID + germanPageUid = + # cat=eptheme/200/290; type=string; label=Concepts page UID + conceptsPageUid = + # cat=eptheme/200/300; type=string; label=My E&P page UID + myEpPageUid = + # cat=eptheme/200/310; type=string; label=Booking links PID + bookingLinksPid = + # cat=eptheme/200/330; type=string; label=Groups price popup page UID + groupsPricePopupPageUid = + # cat=eptheme/200/340; type=string; label='Reisen Alert' page UID + travelAlertPageUid = + # cat=eptheme/200/350; type=string; label='Corona Updates' page UID + coronaUpdatesPageUid = + + # cat=eptheme/500/100; type=string; label=Map tile URL template (without apiKey) + mapTileUrl = https://api-eu.geoapify.com/v1/tile/osm-bright/{z}/{x}/{y}.png + # cat=eptheme/500/110; type=string; label=Map tile API key (Geoapify), restricted by referrer + mapApiKey = + # cat=eptheme/500/120; type=string; label=Map attribution (Geoapify and OpenStreetMap credits are both required) + mapAttribution = Powered by Geoapify | © OpenStreetMap contributors + + # cat=eptheme/400/100; type=string; label=Phone number general + phoneNumber = 0221 - 272 276 0 + # cat=eptheme/400/110; type=string; label=Phone number WhatsApp + whatsApp = 0221 - 272 276 0 + # cat=eptheme/400/100; type=string; label=Phone number general + phoneNumberLink = +492212722760 + # cat=eptheme/400/110; type=string; label=Phone number WhatsApp + whatsAppNumberLink = https://wa.me/492212722760 + # cat=eptheme/400/120; type=string; label=Business hours days + businessHoursDays = Montag - Freitag + # cat=eptheme/400/130; type=string; label=Business hours time + businessHoursTime = 08:30 bis 18:30 + # cat=eptheme/400/140; type=string; label=Contact form email to + contactFormToEmail = + # cat=eptheme/400/150; type=string; label=Contact form name to + contactFormToName = + # cat=eptheme/400/160; type=boolean; label=Enable contact form autoreply + enableContactFormAutoreply = 0 + # cat=eptheme/400/170 type=string; label=Contact form autoreply subject + contactFormAutoreplySubject = Ihre Anfrage bei E&P Reisen + + # cat=eptheme/100/100; type=string; label=Facebook URL + facebookUrl = + # cat=eptheme/100/120; type=string; label=Instagram URL + instagramUrl = + # cat=eptheme/100/130; type=string; label=YouTube URL + youtubeUrl = + # cat=eptheme/100/135; type=string; label=tiktok URL + tiktokUrl = https://www.tiktok.com/@ep_reisen + # cat=eptheme/100/140; type=string; label=Facebook ViewContent 'product uid' + facebookViewContentProductUid = + # cat=eptheme/100/150; type=string; label=Facebook ViewContent 'name internal' + facebookViewContentNameInternal = + + # cat=eptheme; type=string; label=Slider storage PID + sliderStoragePid = + # cat=eptheme; type=string; label=Teammember storage PID + teamMemberStoragePid = + # cat=eptheme; type=string; label=Disrupter storage PID + disrupterStoragePid = + + # cat=eptheme/500/100; type=string; label=Teasertext max characters + teaserTextMaxChars = 120 + # cat=eptheme/500/110; type=string; label=Default bodyclass + defaultBodyclass = subpage + # cat=eptheme/500/120; type=string; label=Logo image + logoImage = EXT:ep_theme/Resources/Public/images/logo_wide.svg + # cat=eptheme/500/130; type=string; label=Force active topnav uid + forceActiveTopnavUid = + # cat=eptheme/500/140; type=string; label=Booking URL template code + bpnBookingUrlTemplateCode = base + # cat=eptheme/500/150; type=string; label=Search page header image + searchPageHeaderImage = + # cat=eptheme/500/160; type=string; label=Rating widget label + ratingWidgetLabel = Reisebewertung + # cat=eptheme/500/170; type=boolean; label=Hide area/top navigation + hideAreaNavigation = 0 + # cat=eptheme/500/170; type=boolean; label=Hide searchbar + hideSearchbar = 0 + # cat=eptheme/500/180; type=string; label=Global concept code + globalConceptCode = + + # cat=eptheme/800/100; type=boolean; label=Show 'skipass inklusive' badge + showSkipassBadge = 1 + # cat=eptheme/800/100; type=string; label=Custom badge storage PID + badgeStoragePid = + + # cat=eptheme/600/100; type=string; label=XML sitemap root page UID + xmlSitemapRootPageUid = + # cat=eptheme/600/110; type=string; label=XML sitemap page UIDs to exclude + xmlSitemapExcludeUids = + # cat=eptheme/600/120; type=string; label=CTypes left out of the chatbot content-only page type (quoted, comma-separated) + contentOnlyExcludedCTypes = 'list','form_formframework','menu_subpages','teaser_product','teaser_hotel','teaser_concept','teaser_country','teaser_region','ep_container_teasergroup' + + # cat=eptheme/300/100; type=string; label=Cookiefirst Script + cookieFirstScriptUrl = https://consent.cookiefirst.com/sites/ep-reisen.de-fabb1554-8932-4075-85dc-97fa68892918/consent.js + # cat=eptheme/300/110; type=string; label=Google Static Maps Base Url + googleStaticMapsBaseUrl = https://maps.googleapis.com/maps/api/staticmap?key=AIzaSyDDOforD1O_j4xXy2nJybC2yQqbUAw9ClY + # cat=eptheme/300/120; type=string; label=Google Maps Js API Url + googleMapsJsApiUrl = https://maps.googleapis.com/maps/api/js?key=AIzaSyDDOforD1O_j4xXy2nJybC2yQqbUAw9ClY + # cat=eptheme/300/120; type=string; label=Google Maps Js API Url + googleMapsApiKey = AIzaSyDDOforD1O_j4xXy2nJybC2yQqbUAw9ClY + # cat=eptheme/300/130; type=string; label=Canonical base URL/Domain + canonicalBaseUrl = https://www.ep-reisen.de + + # cat=eptheme.events/700/100; type=string; label=Event date day from + eventDateDayFrom = + # cat=eptheme.events/700/110; type=string; label=Event date day to + eventDateDayTo = + # cat=eptheme.events/700/120; type=string; label=Event date month + eventDateMonth = + + # cat=eptheme.events; type=string; label=Event herounit claim + eventHeroClaim = + # cat=eptheme.events; type=string; label=Event herounit description + eventHeroDescription = + # cat=eptheme.events; type=string; label=Event disturber headline + eventDisturberHeadline = Absolut
Unschlagbarer
Preis + # cat=eptheme.events; type=string; label=Event disturber list items + eventDisturberListitems = 7 Übernachtungen in Top Apartments;6 Tage Skipass Val Thoerens;Ski & Boarderweek Event-Ticket + # cat=eptheme.events; type=string; label=Event disturber button label + eventDisturberButtonLabel = Jetzt ab 249€ buchen + # cat=eptheme.events; type=string; label=Event disturber button link + eventDisturberButtonLink = + # cat=eptheme.events; type=boolean; label=Force Partner ID selection + forcePartnerIdSelect = + # cat=eptheme.events; type=string; label=Partner ID selection label + partnerIdSelectLabel = + } +} + +@import 'EXT:fluid_styled_content/Configuration/TypoScript/constants.typoscript' +@import 'EXT:ep_products/Configuration/TypoScript/constants.typoscript' +@import 'EXT:gridelements/Configuration/TypoScript/constants.typoscript' +@import 'EXT:seo/Configuration/TypoScript/XmlSitemap/constants.typoscript' +@import 'EXT:typo3_encore/Configuration/TypoScript/constants.typoscript' +@import 'EXT:news/Configuration/TypoScript/constants.txt' +@import 'EXT:bw_captcha/Configuration/TypoScript/constants.typoscript' diff --git a/packages/ep_theme/Configuration/TypoScript/setup.typoscript b/packages/ep_theme/Configuration/TypoScript/setup.typoscript new file mode 100644 index 0000000..3c8025e --- /dev/null +++ b/packages/ep_theme/Configuration/TypoScript/setup.typoscript @@ -0,0 +1,125 @@ +@import 'EXT:ep_theme/Configuration/TypoScript/Extensions/*' +@import 'EXT:ep_theme/Configuration/TypoScript/Library/*' +@import 'EXT:ep_theme/Configuration/TypoScript/ContentElements/*' + +plugin.tx_eptheme { + settings < lib.contentElement.settings + settings { + themekey = {$plugin.tx_eptheme.settings.themekey} + footerNavPid = {$plugin.tx_eptheme.settings.footerNavPid} + mainNavPid = {$plugin.tx_eptheme.settings.mainNavPid} + mobileNavPid = {$plugin.tx_eptheme.settings.mobileNavPid} + topNavPid = {$plugin.tx_eptheme.settings.topNavPid} + legalNoticePageUid = {$plugin.tx_eptheme.settings.legalNoticePageUid} + termsAndConditionsPageUid = {$plugin.tx_eptheme.settings.termsAndConditionsPageUid} + dataProtectionPageUid = {$plugin.tx_eptheme.settings.dataProtectionPageUid} + imageCreditsPageUid = {$plugin.tx_eptheme.settings.imageCreditsPageUid} + cookiePolicyPageUid = {$plugin.tx_eptheme.settings.cookiePolicyPageUid} + infoPageUid = {$plugin.tx_eptheme.settings.infoPageUid} + groupsPageUid = {$plugin.tx_eptheme.settings.groupsPageUid} + facebookUrl = {$plugin.tx_eptheme.settings.facebookUrl} + youtubeUrl = {$plugin.tx_eptheme.settings.youtubeUrl} + tiktokUrl = {$plugin.tx_eptheme.settings.tiktokUrl} + instagramUrl = {$plugin.tx_eptheme.settings.instagramUrl} + phoneNumber = {$plugin.tx_eptheme.settings.phoneNumber} + whatsApp = {$plugin.tx_eptheme.settings.whatsApp} + phoneNumberLink = {$plugin.tx_eptheme.settings.phoneNumberLink} + whatsAppNumberLink = {$plugin.tx_eptheme.settings.whatsAppNumberLink} + businessHoursDays = {$plugin.tx_eptheme.settings.businessHoursDays} + businessHoursTime = {$plugin.tx_eptheme.settings.businessHoursTime} + teaserTextMaxChars = {$plugin.tx_eptheme.settings.teaserTextMaxChars} + faqPageUid = {$plugin.tx_eptheme.settings.faqPageUid} + defaultHomeUid = {$plugin.tx_eptheme.settings.defaultHomeUid} + mainSitePid = {$plugin.tx_eptheme.settings.mainSitePid} + logoLink = {$plugin.tx_eptheme.settings.logoLink} + defaultAjaxUid = {$plugin.tx_eptheme.settings.defaultAjaxUid} + defaultContactFormPageUid = {$plugin.tx_eptheme.settings.defaultContactFormPageUid} + defaultSearchPageUid = {$plugin.tx_epproducts.settings.defaultSearchPageUid} + defaultDetailPageUid = {$plugin.tx_epproducts.settings.defaultDetailPageUid} + coronaUpdatesPageUid = {$plugin.tx_eptheme.settings.coronaUpdatesPageUid} + logoImage = {$plugin.tx_eptheme.settings.logoImage} + forceActiveTopnavUid = {$plugin.tx_eptheme.settings.forceActiveTopnavUid} + eventDateDayFrom = {$plugin.tx_eptheme.settings.eventDateDayFrom} + eventDateDayTo = {$plugin.tx_eptheme.settings.eventDateDayTo} + eventDateMonth = {$plugin.tx_eptheme.settings.eventDateMonth} + eventHeroClaim = {$plugin.tx_eptheme.settings.eventHeroClaim} + eventHeroDescription = {$plugin.tx_eptheme.settings.eventHeroDescription} + eventDisturberHeadline = {$plugin.tx_eptheme.settings.eventDisturberHeadline} + eventDisturberListitems = {$plugin.tx_eptheme.settings.eventDisturberListitems} + eventDisturberButtonLabel = {$plugin.tx_eptheme.settings.eventDisturberButtonLabel} + eventDisturberButtonLink = {$plugin.tx_eptheme.settings.eventDisturberButtonLink} + cookieFirstScriptUrl = {$plugin.tx_eptheme.settings.cookieFirstScriptUrl} + bpnBookingUrlTemplateCode = {$plugin.tx_eptheme.settings.bpnBookingUrlTemplateCode} + groupsPricePopupPageUid = {$plugin.tx_eptheme.settings.groupsPricePopupPageUid} + googleStaticMapsBaseUrl = {$plugin.tx_eptheme.settings.googleStaticMapsBaseUrl} + googleMapsJsApiUrl = {$plugin.tx_eptheme.settings.googleMapsJsApiUrl} + googleMapsApiKey = {$plugin.tx_eptheme.settings.googleMapsApiKey} + datePickerPresets < plugin.tx_epproducts.settings.datePickerPresets + searchPageHeaderImage = {$plugin.tx_eptheme.settings.searchPageHeaderImage} + ratingWidgetLabel = {$plugin.tx_eptheme.settings.ratingWidgetLabel} + hideAreaNavigation = {$plugin.tx_eptheme.settings.hideAreaNavigation} + hideSearchbar = {$plugin.tx_eptheme.settings.hideSearchbar} + showSkipassBadge = {$plugin.tx_eptheme.settings.showSkipassBadge} + forcePartnerIdSelect = {$plugin.tx_eptheme.settings.forcePartnerIdSelect} + contactFormToEmail = {$plugin.tx_eptheme.settings.contactFormToEmail} + contactFormToName = {$plugin.tx_eptheme.settings.contactFormToName} + enableContactFormAutoreply = {$plugin.tx_eptheme.settings.enableContactFormAutoreply} + contactFormAutoreplySubject = {$plugin.tx_eptheme.settings.contactFormAutoreplySubject} + groupsContactFormPageUid = {$plugin.tx_eptheme.settings.groupsContactFormPageUid} + blogDetailPageUid = {$plugin.tx_eptheme.settings.blogDetailPageUid} + ratingPageUid = {$plugin.tx_eptheme.settings.ratingPageUid} + watchlistPageUid = {$plugin.tx_eptheme.settings.watchlistPageUid} + skipassPageUid = {$plugin.tx_eptheme.settings.skipassPageUid} + englishPageUid = {$plugin.tx_eptheme.settings.englishPageUid} + germanPageUid = {$plugin.tx_eptheme.settings.germanPageUid} + conceptsPageUid = {$plugin.tx_eptheme.settings.conceptsPageUid} + globalConceptCode = {$plugin.tx_eptheme.settings.globalConceptCode} + travelAlertPageUid = {$plugin.tx_eptheme.settings.travelAlertPageUid} + myEpPageUid = {$plugin.tx_eptheme.settings.myEpPageUid} + myEpApiEndpointUrl = {$plugin.tx_eptheme.settings.myEpApiEndpointUrl} + myEpApiToken = {$plugin.tx_eptheme.settings.myEpApiToken} + myEpApiTimeout = {$plugin.tx_eptheme.settings.myEpApiTimeout} + facebookViewContentValues { + productUid = {$plugin.tx_eptheme.settings.facebookViewContentProductUid} + nameInternal = {$plugin.tx_eptheme.settings.facebookViewContentNameInternal} + } + domainsToTrack = bpn.ep-reisen.de,www.ski-boarderweek.de,www.ski-wochenenden.de,www.snowzone.net,www.unichamp.de + } + view { + templateRootPaths.0 = EXT:ep_theme/Resources/Private/Templates + partialRootPaths.0 = EXT:ep_theme/Resources/Private/Partials + layoutRootPaths.0 = EXT:ep_theme/Resources/Private/Layouts + } +} + +# Enable section index menus on invisible pages +tt_content.menu_section.dataProcessing.10.includeNotInMenu = 1 + +@import 'EXT:ep_theme/Configuration/TypoScript/Page/*' + +config { + compressJs = 1 + compressCss = 1 + typolinkCheckRootline = 1 + typolinkEnableLinksAcrossDomains = 1 + sendCacheHeaders = 1 + content_from_pid_allowOutsideDomain = 1 + inlineStyle2TempFile = 0 + additionalHeaders { +# 10 { +# header = Content-Security-Policy: default-src 'self' 'unsafe-inline'; connect-src 'self' https://my.ep-reisen.de/; +# } + 20 { + header = Strict-Transport-Security: max-age=31536000; includeSubDomains; preload + } + 30 { + header = X-Frame-Options: sameorigin + } + 40 { + header = Referrer-Policy: same-origin + } + 50 { + header = Permissions-Policy: microphone=(), camera=() + } + } +} diff --git a/packages/ep_theme/Configuration/Yaml/BaseSetup.yaml b/packages/ep_theme/Configuration/Yaml/BaseSetup.yaml new file mode 100644 index 0000000..79da447 --- /dev/null +++ b/packages/ep_theme/Configuration/Yaml/BaseSetup.yaml @@ -0,0 +1,39 @@ +TYPO3: + CMS: + Form: + persistenceManager: + allowedExtensionPaths: + 10: EXT:ep_theme/Resources/Private/Forms/ + + prototypes: + standard: + + formElementsDefinition: + Form: + renderingOptions: + templateRootPaths: + 1505042810: 'EXT:ep_theme/Resources/Private/Templates/Form/' + partialRootPaths: + 1623227657: 'EXT:ep_theme/Resources/Private/Partials/Form/' + layoutRootPaths: + 1505042810: 'EXT:ep_theme/Resources/Private/Layouts/Form/' + honeypot: + enable: false + + finishersDefinition: + BpnApiFinisher: + implementationClassName: 'EP\EpTheme\Form\BpnApiFinisher' + ApplicationApiFinisher: + implementationClassName: 'EP\EpTheme\Form\ApplicationApiFinisher' + WebhookFinisher: + implementationClassName: 'EP\EpTheme\Form\WebhookFinisher' + AddReferrerFinisher: + implementationClassName: 'EP\EpTheme\Form\AddReferrerFinisher' + + contactform: + __inheritances: + 10: 'TYPO3.CMS.Form.prototypes.standard' + + applicationform: + __inheritances: + 10: 'TYPO3.CMS.Form.prototypes.standard' diff --git a/packages/ep_theme/Configuration/Yaml/FormEditorSetup.yaml b/packages/ep_theme/Configuration/Yaml/FormEditorSetup.yaml new file mode 100644 index 0000000..e69de29 diff --git a/packages/ep_theme/Configuration/Yaml/FormEngineSetup.yaml b/packages/ep_theme/Configuration/Yaml/FormEngineSetup.yaml new file mode 100644 index 0000000..e69de29 diff --git a/packages/ep_theme/Configuration/Yaml/FormFinishersBackend.yaml b/packages/ep_theme/Configuration/Yaml/FormFinishersBackend.yaml new file mode 100644 index 0000000..25d8745 --- /dev/null +++ b/packages/ep_theme/Configuration/Yaml/FormFinishersBackend.yaml @@ -0,0 +1,111 @@ +TYPO3: + CMS: + Form: + prototypes: + standard: + formElementsDefinition: + Form: + formEditor: + editors: + 900: + selectOptions: + 1000: + value: 'BpnApiFinisher' + label: 'BusPro Finisher' + 1010: + value: 'ApplicationApiFinisher' + label: 'Application Finisher' + 1050: + value: 'WebhookFinisher' + label: 'Webhook Finisher' + 1100: + value: 'AddReferrerFinisher' + label: 'Referrer Finisher' + propertyCollections: + finishers: + 1000: + identifier: 'BpnApiFinisher' + editors: + __inheritances: + 10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin' + 100: + label: 'BusPro Finisher' + 110: + identifier: 'mapping' + templateName: 'Inspector-TextEditor' + label: 'Mapping' + propertyPath: 'options.mapping' + propertyValidators: + 10: 'NotEmpty' + 1010: + identifier: 'ApplicationApiFinisher' + editors: + __inheritances: + 10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin' + 100: + label: 'Application Finisher' + 110: + identifier: 'mapping' + templateName: 'Inspector-TextEditor' + label: 'Mapping' + propertyPath: 'options.mapping' + propertyValidators: + 10: 'NotEmpty' + 120: + identifier: 'exclude' + templateName: 'Inspector-TextEditor' + label: 'Exclude' + propertyPath: 'options.exclude' + 1050: + identifier: 'WebhookFinisher' + editors: + __inheritances: + 10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin' + 100: + label: 'Webhook Finisher' + 110: + identifier: 'mapping' + templateName: 'Inspector-TextEditor' + label: 'Mapping' + propertyPath: 'options.mapping' + propertyValidators: + 10: 'NotEmpty' + 120: + identifier: 'url' + templateName: 'Inspector-TextEditor' + label: 'URL' + propertyPath: 'options.url' + propertyValidators: + 10: 'NotEmpty' + 1100: + identifier: 'AddReferrerFinisher' + editors: + __inheritances: + 10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin' + 100: + label: 'Referrer Finisher' + + finishersDefinition: + BpnApiFinisher: + formEditor: + iconIdentifier: 'form-finisher' + predefinedDefaults: + options: + mapping: 'lastName:text-1,firstName:text-2,gender:singleselect-1,email:email-1,phone:telephone-1' + ApplicationApiFinisher: + formEditor: + iconIdentifier: 'form-finisher' + predefinedDefaults: + options: + mapping: 'firstName:text-1,lastName:text-2,email:email-1,phone:telephone-1' + exclude: 'captcha,privacypolicyaccepted' + WebhookFinisher: + formEditor: + iconIdentifier: 'form-finisher' + predefinedDefaults: + options: + mapping: 'lastName:text-1,firstName:text-2,gender:singleselect-1,email:email-1,phone:telephone-1' + url: '' + AddReferrerFinisher: + formEditor: + iconIdentifier: 'form-finisher' diff --git a/packages/ep_theme/Configuration/Yaml/FormFinishersFrontend.yaml b/packages/ep_theme/Configuration/Yaml/FormFinishersFrontend.yaml new file mode 100644 index 0000000..fbd9d99 --- /dev/null +++ b/packages/ep_theme/Configuration/Yaml/FormFinishersFrontend.yaml @@ -0,0 +1,35 @@ +TYPO3: + CMS: + Form: + prototypes: + standard: + finishersDefinition: + BpnApiFinisher: + implementationClassName: 'EP\EpTheme\Form\BpnApiFinisher' + formEditor: + label: 'BusPro Finisher' + options: + identifier: 'BpnApiFinisher' + mapping: 'lastName:text-1,firstName:text-2,gender:singleselect-1,email:email-1,phone:telephone-1' + ApplicationApiFinisher: + implementationClassName: 'EP\EpTheme\Form\ApplicationApiFinisher' + formEditor: + label: 'Application Finisher' + options: + identifier: 'ApplicationApiFinisher' + mapping: 'firstName:text-1,lastName:text-2,email:email-1,phone:telephone-1' + exclude: 'captcha,privacypolicyaccepted' + WebhookFinisher: + implementationClassName: 'EP\EpTheme\Form\WebhookFinisher' + formEditor: + label: 'Webhook Finisher' + options: + identifier: 'WebhookFinisher' + mapping: 'lastName:text-1,firstName:text-2,gender:singleselect-1,email:email-1,phone:telephone-1' + url: '' + AddReferrerFinisher: + implementationClassName: 'EP\EpTheme\Form\AddReferrerFinisher' + formEditor: + label: 'Referrer Finisher' + options: + identifier: 'AddReferrerFinisher' diff --git a/packages/ep_theme/Configuration/Yaml/FormsBase.yaml b/packages/ep_theme/Configuration/Yaml/FormsBase.yaml new file mode 100644 index 0000000..f7d0c3f --- /dev/null +++ b/packages/ep_theme/Configuration/Yaml/FormsBase.yaml @@ -0,0 +1,42 @@ +TYPO3: + CMS: + Form: + persistenceManager: + allowedExtensionPaths: + 10: EXT:ep_theme/Resources/Private/Forms/ + prototypes: + standard: + formEditor: + formElementGroups: + input: + label: Basiselemente + html5: + label: HTML5-Elemente + select: + label: Auswahlelemente + custom: + label: Sonstiges + formElementsDefinition: + AdvancedPassword: null + DatePicker: null + Fieldset: null + GridRow: null + Hidden: null + ImageUpload: null + Password: null + Form: + renderingOptions: + templateRootPaths: + 1505042810: 'EXT:ep_theme/Resources/Private/Templates/Form/' + partialRootPaths: + 1623227657: 'EXT:ep_theme/Resources/Private/Partials/Form/' + layoutRootPaths: + 1505042810: 'EXT:ep_theme/Resources/Private/Layouts/Form/' + honeypot: + enable: false + contactform: + __inheritances: + 10: 'TYPO3.CMS.Form.prototypes.standard' + applicationform: + __inheritances: + 10: 'TYPO3.CMS.Form.prototypes.standard' diff --git a/packages/ep_theme/Resources/Private/.htaccess b/packages/ep_theme/Resources/Private/.htaccess new file mode 100755 index 0000000..3418e55 --- /dev/null +++ b/packages/ep_theme/Resources/Private/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff new file mode 100644 index 0000000000000000000000000000000000000000..a18ddb12e640567f16435f404ebbb2b609603a47 GIT binary patch literal 30024 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 K0m#*20*WZH72V6(XEPqv{J_IT$ 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff2 b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-300.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..a7f756c4d1251222f951ed3bf82f8a06c83f4334 GIT binary patch literal 23248 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-700.woff b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-700.woff new file mode 100644 index 0000000000000000000000000000000000000000..abf0196d8329e4df2795f2c1a94a031f5ab757e0 GIT binary patch literal 28052 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-900.woff b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-900.woff new file mode 100644 index 0000000000000000000000000000000000000000..6f201e0709c4c86df46cab5e6e98d6afa9cea08b GIT binary patch literal 27524 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 T_o0XQ1>YM(H${yNvnv_}E{scw literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-regular.woff b/packages/ep_theme/Resources/Private/Assets/fonts/lato-v17-latin-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..189a0feb590a6a6b77b54c8edb09cb1512c3ca60 GIT binary patch literal 28660 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 F`4!raiah`T literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/ajax-loader-white.gif b/packages/ep_theme/Resources/Private/Assets/images/ajax-loader-white.gif new file mode 100644 index 0000000000000000000000000000000000000000..a41635452702969b05af24822a3647f2fbe9fa11 GIT binary patch literal 1737 zcmYk+eN0pL8o=>m!M*@OE9X$9TPffGd956FTKYy$-)W_Qo`GP2mf8xXYez2#SFc8Y zr{_2wt|vN;2BXtxG&-0JXQRWRIGIKx)9G|NjYgxSFc_j3kb|~*w@#0>eQ*Ot}e6LeB#830|yS&*47#f zhDVPcy?F6r%a$!WckX=o^5u>lJB}Vb`ug?jH*eldOiWz9eEHR@S2u6o92*;Z_Uzg9 z>(?JVcyRab-K$rxUc7km?Af#T?%lg}>(=w<&u`zp-QV9oK0bc({6PQTxO;lhQ@n>Twrp2v?Lx3skE+qZAks#Q*>v#zc#8jUVpy0ogQYWMElYuB#z zdcBt}UAl7R%EN~bPoF-$ZQHhifq_GZ4y|0da{vDQJv}|$-Q5ih4I?8XHk-|2u^c;g zOsCTg4-Y#W4!hmn-rl}x)27zeR-@6lVZ(;{`uY_sRxk{+efxIk{bc?-T_5tex;%>{ zJ?&j>5|_`_>kqmX6}JbxK`8T8Je2VFYUbNSyOEf$$7_tG#nQMbe?Jwz`{7+gh_h}b z1jsN&iLz%G(G>TCS^32PQ^=@<#1B6zC;>958Y5Zk>0%m#;Z0CnVrn0xu26&cB*74> ztpHP}!9$SJ2-wNUkPO!VPD&vP+z}(tl`qDWzoz04&r%V?1#e~YZ`UOb~n?y9En(;|uLy==vOAckN zo^KJrgta-}5jMxy^i!o&0y~=lGSXtJ9flMbh@Y!IVlsi;s-K2o1PJZ;u%h)0RLSQK z6WZfYg>YUvUBWUP1?8n>l{|%_#OM_LO5!mp^lux#*T!^llXa1pt|Cv&w`cmzG*m9+7Rh0|7V8yFa*+6tx@ZzHek-v zMD6+6z~q!?F`rGt$r5LImK4bTf;uVK$l_%YY7`6H|5{>7nmS`jaP|i zb_G4+96<#Q@d`RU!ZJ&0X&PlvC8TLll$G!iKbcvUWtnq;H8xr+dX{>n7@{G{v}m6KvO z)A0oa;hr-UM-lXaiR63f&TN{up0N<#|5 zz+>S$d2JSjAteY3$q;2;r8X>8D`<0u6#+8z8>ZHae}^%^P^cQAP_gHD7%dY6cJVQW zH75s@<~NTtas)t>>=4o_`@i}@V@FZ18%EOVmNv^NZuLJyOOjK3%k&eTnJMgKiN$$# zfh5+JGa2{A|8TTSVL}iSMw1w7f?Ev+HI+jvV51Ty@oB{o0@t7*JKr9GcxeV;n^iCf z>&ro+gBZlDZje;;V?>d02wL2bH)v=S08e7~pr8;3EZc%kI!(wOhk`X-rL}4k?#~p8 kr~*l~Z*EmHN9>B2xpQXP9oj!nY&?#yf*|F)!}s6$4}UgXkN^Mx literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/at.png b/packages/ep_theme/Resources/Private/Assets/images/at.png new file mode 100755 index 0000000000000000000000000000000000000000..3ad010b7e5270792a4a51c6cd3d5883d51f4373a GIT binary patch literal 133 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 T#s;!1?(MOQcZ0>E{{a8l$r!P& literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/background/header-winter.jpg b/packages/ep_theme/Resources/Private/Assets/images/background/header-winter.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5a46d2d3d9d44b4fa74b2997c1413de7b7a513ae GIT binary patch literal 24460 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 E*%DUHX8-^I literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/background/pattern.jpg b/packages/ep_theme/Resources/Private/Assets/images/background/pattern.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dcf82cecb13cd876b4775be4f78eb3d53b2bf6f5 GIT binary patch literal 32480 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/background/winter.jpg b/packages/ep_theme/Resources/Private/Assets/images/background/winter.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1a96b54289b204985fde0379e32a6439ba6c52cb GIT binary patch literal 79967 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 Z)(EnzjCm39=DK22DuJsT*Z*(v{{eI)aAp7i literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/badge_skipass.png b/packages/ep_theme/Resources/Private/Assets/images/badge_skipass.png new file mode 100644 index 0000000000000000000000000000000000000000..8f0d3289aa96df84df75a86d5cdb3387e85b4ba4 GIT binary patch literal 7513 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 Bn-u^6 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/badge_skipass_black.png b/packages/ep_theme/Resources/Private/Assets/images/badge_skipass_black.png new file mode 100644 index 0000000000000000000000000000000000000000..1196b5c78e36a4e79ced8dee38895282fbed8a07 GIT binary patch literal 3601 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 Nt)Zu0rfT={{{WqtxoiLc literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/bg_login.jpg b/packages/ep_theme/Resources/Private/Assets/images/bg_login.jpg new file mode 100644 index 0000000000000000000000000000000000000000..10588da63774034883ed2204b6a3db6fdb40d4de GIT binary patch literal 30995 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 GF#Zp4G!_~F literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/ch.png b/packages/ep_theme/Resources/Private/Assets/images/ch.png new file mode 100755 index 0000000000000000000000000000000000000000..97c435c7380a14dc8e79cd5f23f7df129cde976a GIT binary patch literal 242 zcmVk`B0V|l|9?x=U;#gQs(l|-g)F<4Qp7R#9n2gDk_2hW7%WSK;cKr<@j3}u sz|a3AmgS$}AV5)o(o^#sQN*?92ePu@CYj!dApigX07*qoM6N<$g6Cgkn*aa+ literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_1.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_1.svg new file mode 100644 index 0000000..2050381 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_1.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_3.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_3.svg new file mode 100644 index 0000000..ae12231 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_3.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_4.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_4.svg new file mode 100644 index 0000000..219af6f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_4.svg @@ -0,0 +1,12 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_deal.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_deal.svg new file mode 100644 index 0000000..af8fc30 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_bottom_deal.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_1.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_1.svg new file mode 100644 index 0000000..1dd724d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_1.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_2.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_2.svg new file mode 100644 index 0000000..6d70447 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_2.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_3.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_3.svg new file mode 100644 index 0000000..0f00471 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_3.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_4.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_4.svg new file mode 100644 index 0000000..1cb85ba --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_4.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_5.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_5.svg new file mode 100644 index 0000000..7d8a48b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_5.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_deal.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_deal.svg new file mode 100644 index 0000000..c8215ac --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_deal.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_hilfe.svg b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_hilfe.svg new file mode 100644 index 0000000..94aca0f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/corner/corner_top_hilfe.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/de.png b/packages/ep_theme/Resources/Private/Assets/images/de.png new file mode 100644 index 0000000000000000000000000000000000000000..4b06ae2ed481d4421f1a182dfc506bbad5faac31 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!2~2P?myiHqzXM<978G?-(F{AYYG%N@^R)? z&$oiR{(J6nWM3)3zsJnV!FI|+6otJXV=h-@Sjg6;i)daS# zzi>j@gsJ|~hQpSj4`UshKJ#?;OxEbx{rrnGpY@#njRm)wNP4@O1TaS?83{1ORrrK~(?% literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/favicon_ep.ico b/packages/ep_theme/Resources/Private/Assets/images/favicon_ep.ico new file mode 100644 index 0000000000000000000000000000000000000000..51a262643827ece77eb0cabeb73fcbd0db0471af GIT binary patch literal 4286 zcmeH}YeYTCzd6tQyw5rR zKaZYsBvSB`mq*$+{h3CTNkoMJDJlV4Ui;MGU55ETMiQ~FSO;Pq_@+7#ca_a_l~;}R zaE-+sXiRkn_FCNjYETK5gMzPSxQly;m4ci_V>WY1+W_wfrPK- zse=pPrviS?jqz|0UKsisXF)m0AFY=M&T&&-I!vYgjbqjh+G!mhpt2!5&3)5A`A?cs z6~lGAc_m%r*0eT1>%44$mvqB9y5vnYXEtaBLBN7u#JAK5BRp_sy!qFW&>ON*%dm|$ zKW|E&cjD0E_kH(rfu+q~4^7bzZR`iXfn1$r_z_x@!LZPbGWzlEey^H)?Z)pDPrbEM zJ=akM*U|@nkI*Uvr-1C^HJ-PJMWq019*m-?^`aKc0g2MIk#O0?W0rl#H>T?I+B5XF z={#tD{sK92J=X06;hszJAmaIQ?MLx#UW5Hn@=^Z(1hjS-czB9jmY4&L7D-d~p^6%L zME?sPInzpCIh2`&f8RQZeQ-p6uaBYSHSpjSb zN($!?Kr0h01!@Fcu`iwSJP|`{73kLa=)!ZUs2Lfr^HlP3MPfVSehQp{R^KP|gswIY zfXN@MCp|zb4wQo{AiO7V?wlWJh+8C;lGzi=rfx0+d$1Qy;{NPPaR;??FJR#3ALI^! zsqz}pV(2db2f#DzJ%$IyIsC%sNci=xnrVF3baE2*MLw8?9u?z$C;`)xJwf~jKDrO| zO<*CY?NKK_yxW|~I0ty3fqMKlvIlKc*57D0JPSvs7=0FeFz^d#HS`}BQTIu_-Qt{=IGGwB7 SrXdTvEX+aL#q6?1l6eP>9X#Lw literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/favicon_sbw.ico b/packages/ep_theme/Resources/Private/Assets/images/favicon_sbw.ico new file mode 100644 index 0000000000000000000000000000000000000000..e8b01b648c078ece59606cbc9bdb3173e1f80b13 GIT binary patch literal 1150 zcmZ`&drVVT7(Y5924lcXpaP~m+CrtYz3r`Sp)c<3!&Tbb7FrNA8a0`5nMSiL8nZ-$ z$(Ch*)cvtxG}*>{;Nlz_;tFnVowyApOif%g8Y!rRVZ<`#23q&qxsoo1z&ZKO_nqJ4 zd^smMge;&lCx_6rm~6`+B##hML4!gRnZaoy4$8g0WG2M@e52xvdl}{Jg9lWnM@Gty z5C5$A{9es`+Sz#3v7s#W$@^WV?r|8qC!pz>K*iUiQ>tS_n`Ue0+B2sf9APgcp=%q4 z#(EakH!s5+OQP!R*feuun4Qa$#!O#5G#O&!P#t*yE%Oaz;meSdqcAtdVY)a8_RMHk zT02*^bdDX;UHB7Kb>G9{`w`~%5-`@p!R8%DO&hIyo;S}+)Oug@7_jbI1cBY z0eJhSVLSZGTeJPL!ouQ$C0QNTXe?#xO+mRK4vq9P7^LmcbLX(i(*;Z05Ik4@h04Eq zv@mPY_VgJpj^ABbt{j!-=E8cg1MWV6`!)&w@F3hD+(huB-_dk>1a+65;*H*EsJI|X zR}?_6VZI9qf;m!Oj~b&HilQQ{Zr+&;^-et!`ce@3pCj1!HzNK2pyfKS?#eUxPTok! z6_rpcSHZ>eXl`m6Ti4i#fX@$B%OsQ)l^WLJ?Oh$(0^!T6?VAQ(9Nl;|^$EZ4(*c90 z9vXvXt5T-eX}4JM(&zU-b#cyddd@}8QCnhS4FaoHLzb62pkN&f(|0V$$*EJz z+%X%B3j;pi_DDm+P_@x~yV__HzY7Tofef!_%^hbg_d-@&wma<=bAI-+e3sGuu*T(v z&%1Vyh}C($OLZD%nOKWsLrcrsG~jW%Zj`Jnj+RMFB{K{m@)DVszHw($B=Q&4JYL`2 z{68mZIJ;vp^$j+L!!YRBm}GhW-Xck1X4+3{IgJk5``4gf0Pm=MC(VhOQERjGTHS9J zQ}xHRx4j^5Md_=&SWA{mY#NoimfrU!)$dC4Q}$pqDJ3MC9f&5f2}xuIqY2tMkr{~Y ge@#?{qBGiHbisZ?_=6oE##;$F(0XWP>m@?|1FM^4LI3~& literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/favicon_ser.ico b/packages/ep_theme/Resources/Private/Assets/images/favicon_ser.ico new file mode 100644 index 0000000000000000000000000000000000000000..6e5d52ddae63868a7008553c23f4c32c6e966a29 GIT binary patch literal 12014 zcmeHtXH=Y7n%*6+*IwIedz{z1YkQn`9mi?C6S@^z>Q*L+ROBR307)Q_kXivHlmit3 zkwHQkB|t)CDp!$nrgAQds-iOZxkY!Mp4p!6_1Saw_=k%M-$#Alz3+YF^FFr-`4Rao z^3$IZeEuG3{6j+igb?zRpL})xyM)Z+e?R^)J^vk&_1lD8{bNG@2-n~il8hI)^P0_eWRc2>w)0&tMiTje*6FU;#(L0+GSr~{{5e8bc6tX$BT|l&(;F# zYk{>uz#s4h);)oM7bhOSZ`rYGGB2ytj`11K@Wkr);JW76Q+dca3T@(29%;q`cR2D?yhZG57; zdc$}qPoHvF7j?i8nW1H5s2Q1t#3P1-1*R({h9_^d!=o!^i_7o#tz)!+*T3fV`ym+y zhmgVbe!oCeH-2bfLnH_hyvrYeX4V5vk7sc8zv;E5OR8{^3^^;e|*(Imjv*us-P;r9F$ADP*4~es( z2;2}@#|>*f+)h7y{)yRDR=FwZfM#pB?w29@?GcJSjPdZK{)~+7!kibEPt+6_R1_Cf zUOZ8ID*N?;RDNvS@V>~oEn$#Nw=+_eo};OKY*MLRfi*7%$LK+w|A06F$V_bot-wK` zRzG~eV)MUxV@b*9Uwt|-lxeTFagD-UFJmY%uSS&}N`G5DNBF)LBbP7rb8M4Z@OW?~;RNz94wWhF{D zu>x*fFDF67WxTzebM@qdq*NibZ-{lXpz@*&xK2CfeH;&I`h&j=c2M6Z*YDeDQk+C6LJf9aM;>L>D z@erw(6EEY&wA{|QaP&!J?CADT?Upe8#ak+s&Ow*_U5{_1giQnMUTWTee{9B`k+-xt zO!--;E-Ry>@ydDVfzL|n<}tcBi6W|*cs@Hpz)29WqlcR=`@D9uazId)B;dpe*oh)` zqLdv6nW=1?I58)#hs&tAa6LI${ArkBTd4ZXMg5Y-iSz~c#shDdbtB}yG9!`#%@jx) z{yBy7V7_W=sOGas<*6etTkaf&fuMY>8W!n>&v4>}oLDiN(Z`POVMkBBy6p2g45~2^ zFImWrgPgsrBq5bHUd(0mK})P;erXhrUlgA?l-{v9)QEWe@}^p6^aTBs`mKk~Ph;H-LJ?~s7U5ONqon1B~8;lxRqNm4dL_#jPEn#O0xb+Ted zny!1jPLpaxR+c2=M2fiaq6b-GULpj;jgYF3lPJM4p4_lo5)K0j?_(!+Q`5kEW1#^-*+Kny zn`uGT|K?sVCvo`Y6_0n-tQ--QCP-^@7DP{Ny0M`aW)Cx7#D?eNJps-y;>E({dP`$p zUq6|a+V@GQAtX-S(qZwhd3;nL$l?oTa2!BQ5Y8_hv_)jhBM{<}q%Usf%h>U70|^9R zCv>ut1?*ILWvZYoo?o7&o9qfeeD{iK^!;p4z29p$>E=c|s+P4Afq-Y#ZWw81@>vW? zX|lL9MF8LCG7xtnRsx@k0TXL4U5bny+q%b`QJ|Prx_pReB$zdyJD9quJZo#F70>Bn z?H8fyz0p$@=Wa^4u|3EtUj-3E%Gp2i?2K_iJl4V#F&To&LsMNfR>PtfRlx1^`F(DP z=d(LqcI{N(@XL!5Zi28ZLm_WklD!b~GU4YURuZ%%;4oy|=pI&5abER~2*tKAU3sm+ z<%Zu`zLBuw z1+2I(IBnfYrTn?uu0yr+_`DX?(A0<9ePx-wtT-_{R#KbmvMOw*B~jf`F*6=__0rUo z45P!fUfw83OYiw?uPN!!;@HG0Y8*|$U&~{+dy8+WKMU3Di=D2!QY0?hFJ`6A_tY%N zUdtYyf=>!d7-JtetA5X%?3tJw1LY&W;2S-h#L2cYpWm~rpB7dh55{{ut{1QwqS_Oy zRGvjaL$iE1Fv;<9nEr@BDikhj$T+G#fdBPgWL5foBxbnC5S znHe2_8lpd3xS-O)i@ZMnSLLd)N&iLU()KWI>4j@@ZZsl7$cZ0$enB_GcYB;}pG!AC zsGjI@`8^6*ql6bf_^e1$ktwb}t(%v-ohD>|pV#TMDi&l-vhvja#|7gpcLo~rT-G@| z6es_%Vw?qNf;@`;*Mqgkk-kvo_`K+%6V+Qo40~gBy@D10y32#uUthP`{TFX7ZVS=I zCrO^(C`1(yFjISJkTOK&X=ANrCe^sh=XCqM-nBIhD6T%HR}M|Sy)G(Yh$=D_^2W7* z%VyS(yuRAUOA)bRX1X3&6+@Dz#ZIfr5Bx?W(W0CZ4n4mt;w1I5k+B)wtQ2u+WZ9LA z;j!ae_bgXD(tEr<3i{U97gg@`13wC|B=2>F;Rp1SaJLkhWyDHt9}$I z9m&%HhZz27U0#}OzA0fv^)ML&O;=GbjEV^XFA=MdvLohts@yh}ak1ay^&nQf0q?Br zT~BFN7n8ai$_JjPba;PtHzz&g1nf9jWu|JR-R-fvd>$-z_|5IU2S?`QuRVU6QIV4k z^Wx#>7sg&)b9)?S)qtcbd!eVyW?pc^NIs9t?HuoYDsDKnD0}I47?FGJ#`%fn+ekrt zb|RmX&{vW+zze^0y#D8*#@NiIkx@rr9Yxmj`n_ROsA*sHboIrn{iXY1sXlHzpA|R! zFmKs3Z!^qHwU@~&(uM4((T}Ch3U|R5W#WW4691e?BIT-M*x@-oEYSi!dxV$dy zv}m}cL^ayFVqEBdd5O=C7BybfO?CUd%bKw^AtynKGK74~O2Ks33n~C<_Qq)ha%*4> z^0;5THGCRk+0Ph#bom0RXAe7B%#96}i6q&hJV8}9qFabk&WRs*c7Ev1Z7DB_&x+#L z<}S%!+8rhe6;Kb?){#Rj+L4j>Y(5X^G)%xtkUuIER%8MO@G$}}x&PT!QFXqA9Rx_+ z7-&gc8g;XcbNq)hnh2AA;HE@drFU`t*9;HqCga>`9UQ zVZoxjSu@fud5}54j-t`@=&VBaWZ=!sPFAv%gLEC+%T6BTMcgW=K;GLOr|6McuqMdU z{L#84+`1!bzUo4;l#6PWAml~&vEqjtPB<(I_@+fQHqm--pgeJ~As4>kS~0E|r+mIu zkJo9K;m?ViJf2mXZeAgN;$ncH;frK5l}n=IEU8iQTF}(NvARgXRGk zU;?C|AznoBiJDFOj1lQd=@^U!c{~ECb6>pi7a_Wm)AwZ*`X=_3M#H$sUHz}F>E@-Y zUZ>Y%(M@)awQyA9A`kk)wRM|*cKRbrRGKvW@{-Hz&?`nHWeI~13ss{ZeI6v+b*oW1 z)6SdjD7R>5kb>-Xi+b=)e_amN3)Ay?4D=>qPVDKUPk*u3oN{z&Y7xN>dHi#WuH+*N zKigwEf2_WjnFdtQTN;CsfqaP2=&MK@YcE|k>V0c|pWo#PxB+igR~@78OS?vcEUFPfX_AN)-Cdb$(M~z-W|zl~dT0CRxBzEcoa{|mPy)nNo@rJL ztt_kOM2{skxdK+y0KTtDqgTr zr@RWP@bh;oyP}uSKNjUz_m*TJrOO}YOCO#=p+R94v7;tCN}T~OOPp%}V-#$F#DG))L4J+Vo?WQg6sp~mFj!Z7wx7)a0+a+RMHed-IeDt> zzQ?yRCuk6}V!N3s;)jJxLoE*HGQEF;cnbKIbyG9#9BFwv`c`pWfy<^cD~81nXvr9W zkQ_!Y6UhfnSpx6N`yr9@n?tq5OpV=vP6xwyAqMCMAI0`SuAY{UwtdmsPeM%D2R^pm z$>}MLp)~@znw8v%gkPOA(aw_A9}{s>raG&gRwZ(&!@8iI68k-Fr&Vj1<9oa|)Kk-( zK-yRc=sxj*Ir8Q@O*PC^5jqPn59}ml!Y9{HMkmRU1`{%s{j{u8Q(;j-I1d&L;!(%8 zJKb0BYd;Us?AmAk^4QZ3@F&5}RL-HGNC-d2(%3Q5szWx-u*YRv9BLER6%4c_*;ONAM+(`MapgcaG)iLmnM#*F}7unqtf^n;q!c0cQE_H*y+uBps` z-k6utJCra3K&0dXp0r8zd%-BIdwTkvp&3e8HY9TH`iYuOc2Xw?@ChA3ENz*&2riWG zI01fSkqNtexqVs#-qZcM;1dfEQ$~i^%MtEw1b2k`;nP;^60Z)Qff<^by7W} zSjDukR4|^X81(O(2kq$LOCF%R_HJuLiVEbjo>VI<5X`P$(T$A`0 zAYOJtJ3G1IQqi%@Hh|&H;i@mfwWqJ@<`!HCYU;E><@_d*V7uT&Xj_AfOwi>Ql&kkS znh1s-d1>qZrLefsyhHD*imtTZPw!a{)xb3PXFDt9?VUrsGk3EO zbnc0r+!U_%ym(SK;K37w8m3NwXF}FVJK-d1*@m*1K z+xO`|!S%_COSiSsA^R#Kn?^Bp;NPT{Z^WVj7j*U@DFksSn+{h3>{$!=j3#$$x9Q9^ zT||a*Th#ogp_)%XhKFi)MJRUcU)&b8_<7XA&d7xw5z1}h%FSWw%^}(yQHuD3s$!;mKU5?xb^eqzqLVt3Qrk0k|@9@PzvYRd6^ql)i2 z_@RsF_b7V~=ekbs9hmG9N-i_O8tU*2h9*0Uh@96pPKD{pUGte@%eiaj%l9mtYRmg>%lwklJ1L-yJ=%J!0vLNLADU?d3cA zUXk7Iq|3pL$j;w6!G`pB#|$7i2Zo%dF6nnf>GniUAIj*sb(&Lm<-*Gw`EPC=dvhxn z#CrXuqU#0a>1o}&Bj$E9v=?p}$7h{b!Ecfu+QWA!jcBf(y|F~3YIjB~y?|5t|-0k*PJ~r)) zQb)%Pl%Kyz2`nBOszl%qly*^RBIx_Mz8cUpl*j<wg}2nZJM#Afj4M|`IJTh z_lgFCVh-&3G+e$2sX!;aYX`F5C@?|4qN|~<*dwv;i`PXbNnTzn*x2u(GNTD8%UBT9 zkG{Jrc#s9c0#v@NB7Lr>di*_h8Yq7a87Q^KV>Dbj7ZE!eb3i{jX{TGf)EXNewWc?@ zvrnsG&FV{+f&8Fh;A2twf#GL`!UtK(!FQ`GdfkiwG>1_&VN?z~tg6X2F8Z{=M<)f` zq#z2XaEHgZ| zd~WjK>s#g}xfk8A%cdS_k%91H#f~@Mv8YESRYycj+KGvI@tyb6j~#5@9j)!?v7yh$ z8lW7L*>j{oi>1A~bzD}StQ>5f?W+TAJ@Ts9fk7i@-k^?0YeJ{L0g zGGit_a8^uwKr`99>a^M|3Pt}bS@j`#*EF?S5U%!B z-|1rGzUa~BJBL7Wff0kZ4>X=qPxrZ3tsv^{4)d~cR^D);my>EzjV;Jt@tHBBuP(3J z^e(3vhIXym<^+%72IyvSFRc8yxNz70nLDL=*vn*DP0uqh5=C7lskDJ%Gsa&RuUJ*k z&)D0WBdtu(9M})A8RllXs?ertC%P2k$99`;*`%`Q=0;lCv;2C@0yb7w3xf5VBf)_d43=5rWht@p>l5Vu$`2A-YmWue0_nC_?q&nO1Pz-6@FI~Wr;kX>lB+H5+Ef7qOs-ql%> z4*jEH79qn_XJd~O?9bq%0(6xEPU6U$o3>>GTpCGMJ3k<;&lhr%2Ogf5RvtuK4gyPF zb+C_<5^R|mAMRzvGBCd0YO@>7&Xl8;q-1_qNjj!Rr#jhu$7Wny5I*g%J~GwDH7*Q* zOmx}};GFHtI-7QSMK^6XFX?Cc^s{`}W$0nPa^xf0S4mwiOn{8>@y@~Un6bP|dYjW} zcX;xP%waJ@AMPAN8bM9~6FS{nV>QlOHDjugj@8u_r(=2QBTrnBWmb(W%A4d>hm^x@ zzJSlFn^g|HTQ)0T_sQ0K0`!3hOm5tZ>-jtP&)i@cuz!LO<5cN)hRVO9(d!TFzjNy}MjDotEeGmMIhS9kB9%RQ?zT0OLyQ`zYXkfWR1t-GUi z>8XO|J4fig1v*|-K+szcGIZnZ^OBd6`h2T;R5kFX|8b#avU{el0Xzcsus2E=GQ+DI zCm4y+utcp;wz7d$D43vSl0p}k$8)dJv?W4)^gvtloov)rG}4rJfYH(LV@IEtYEh$p zy%92_MchQ}a|BB#{V{=v7lYiMm?GF7seRaF_HCe{-~woQ!~?5X_gQV;8%*P85gP34 zKe~F3)*AXl6B;sj$DSE>nz7e{$^tj0^9F^-PY2-JaS8Grk(%;aiwj-zpLtMRL|dW2 zvdzn>GVfxj_r#80%CBy|4H`XZgQUQg&&ca5^2S0W8Spsx(E}Rzmp4wFJNjgA)MQA4 z{^4`06T7iN*zjvJ1eZcL`*GlQ``S9Kna2&=_A4S7!{?4R)?B=VZSS@_N4oB%w%<$p zaQE=D>nF=DTq`{CG9+f~vq;sE(>lNdH(GcK6~6010T602#dtWtV$@n!!*kPteBI7i zw5AGx!{kI!N=i>kYENQ_Ib0#V1l<$s6ZwBZux0b8k{?5Z+e7F z{YJA(AhA_8*otl%vkMg&dFlfvR5@oVWiKD*Ja1{$1<&mH**Z zm&<2aUNxBUbenCfPV78VMf~u9zupi0WuMbN1wXM2_A2yumchr5Ha@<6438xvO_#}n z_zAgRY9#ob3qb-StOrIi(Q=QVcoO_tkX*=XBvd>qepSCEg)qhz+HnjqPbx@T7MvDZPwJO)9% zCbNqk0$!S6FCSxjh+^O!DLmdx6dDitGPj8=>t+ZF6)CGVlj8hJ(t0m-AXg#^DDfUNNZiHywiYY9L*Sr9Z306lW*+&ozjHWJffKRI)8 zi7d%q5lQu7BE?J5|CX3F6GVn%3qGZ_#|h$(yn62-s*w)_u$Q#lK1k3Tkje%tseEK2 z!%t3<>P9QUW;m&RY9**9WVGo5nHN4L;?iU?IqM;)PGt1WbteDf zYa%Fg#H=1C7QAHD*`zS{EqQ%2hk(=~2w0*VeoyeL5~9+1G4DFj;1lo(bFY(reBL@$ zN^aw2w0KDeD~Aj{J5T2L&=)tJ6r6=0%}X)YHP}0qfXgM%-#G}jJP2|&xtLo=F5#sd zhJMxpWT5^yDXp=>9%W>@or^Wf$%=82Om~!#nLS?SGK@`zQb78&*%%fBMsZQ24JJ zb{zgIzq3CjOIG&pKDqkOUjEkRz2!pTU;Vv%zso-G%U}GBqW}46@6BgFO8o2}pSt#s z9#`Vt zdZwlCa5N`|P)fciNA#}!kq*J+OB zZQ1|T6|h!86VH8Pnmi&)o@g!)sAwjJ>TY;$K1y|0KbiS1@;>ay$lnr4lp p8Y1dR%x;aM_8aO`vNDggFfY)|8msB9VQ**WkN^9w^H1Ri;uqJe?MVOt literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/favicon_uch.ico b/packages/ep_theme/Resources/Private/Assets/images/favicon_uch.ico new file mode 100644 index 0000000000000000000000000000000000000000..96a30c851329de1647a2ae29e508f1ac79f3329c GIT binary patch literal 2238 zcmZQzU<5)11qL9=!LWjZfkBLcfk6X^fkF%pKnxPc0E`fk|Ns9pd^#4*@b~*mhM!M1 zG5mPAg5lSTy$pZ8+++Ct<^;p%Q)vu;K3rz_dbOS5>%|&|?|0`ie7iXb!?;m_(GVC7 zfuR=yj8p&|>`V~A0yd0;8Omj1f*8&Q4;CJf04to!0G4M#@Ie~55nPBoCz1dcBO{V> zh&&ULJkSNm%E9I%Ni#4rG9oJ{UY?y7Yz$048#9=T(9aAhPhs*&~WV5uQ2 z4@yI + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/flags/ch.svg b/packages/ep_theme/Resources/Private/Assets/images/flags/ch.svg new file mode 100644 index 0000000..f5ec8d4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/flags/ch.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/flags/de.svg b/packages/ep_theme/Resources/Private/Assets/images/flags/de.svg new file mode 100644 index 0000000..1acf302 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/flags/de.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/flags/fr.svg b/packages/ep_theme/Resources/Private/Assets/images/flags/fr.svg new file mode 100644 index 0000000..712c8a5 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/flags/fr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/flags/gb.svg b/packages/ep_theme/Resources/Private/Assets/images/flags/gb.svg new file mode 100644 index 0000000..132dbed --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/flags/gb.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/fr.png b/packages/ep_theme/Resources/Private/Assets/images/fr.png new file mode 100755 index 0000000000000000000000000000000000000000..884bb028cba7877f5816da128b5e3b0034d655f4 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3HGb?#A2&QZ=3~jv*1PZ*Oemb8rx7dHCAZ z*?Fx~5pVCU%bFrwz3zKtI2{!YPbV@3mTRWOuL8&iU4^3XLp69;n2R^at0EFXon;bUAme Sr*#>wj6z_E2(|O-Ne%JeZuIu?OpXANcZ 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/gb_grey.png b/packages/ep_theme/Resources/Private/Assets/images/gb_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..8bc7b3c5ce9008648972b1804157a56ef0a41a86 GIT binary patch literal 3465 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 MN#5?&gfQ{H0C2SOd;kCd literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/global_compact.png b/packages/ep_theme/Resources/Private/Assets/images/global_compact.png new file mode 100644 index 0000000000000000000000000000000000000000..f50c779b1291feee60b15bb357ad81a876f82df6 GIT binary patch literal 9028 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/global_compact_weiss.png b/packages/ep_theme/Resources/Private/Assets/images/global_compact_weiss.png new file mode 100644 index 0000000000000000000000000000000000000000..152432c81eed7d72b0f742e4239db31658575069 GIT binary patch literal 23487 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-128x128.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..3ecb2f6f8898bf592a6e6d9d58711b6a712c78f5 GIT binary patch literal 8076 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 Vvk=RNe}!9slDsOoRMs@`{{cs&UL^nk literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-144x144.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..11ce01079de9e0f322d2b67a8d31387f2542ee00 GIT binary patch literal 9023 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 oo&P)9HVgkAh9>xVt+9clYJ@Rqa-7eYLfJ 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 X|94o3@%w)gbO4f~aw3&N`houo4^2l1 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-180x180.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-180x180.png new file mode 100644 index 0000000000000000000000000000000000000000..45abefb88e82c03524a5fb3267014d0c6e58d9a5 GIT binary patch literal 11517 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-57x57.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-57x57.png new file mode 100644 index 0000000000000000000000000000000000000000..ad2c3a7a83aea3a24f6f250aaccd8b34db680362 GIT binary patch literal 3660 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-60x60.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-60x60.png new file mode 100644 index 0000000000000000000000000000000000000000..c07c9ba38eab6d5e7d70dba312496413f2ca1cbf GIT binary patch literal 3854 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-precomposed.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000000000000000000000000000000000..ad2c3a7a83aea3a24f6f250aaccd8b34db680362 GIT binary patch literal 3660 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon.png b/packages/ep_theme/Resources/Private/Assets/images/icons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c07c9ba38eab6d5e7d70dba312496413f2ca1cbf GIT binary patch literal 3854 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 bi|i0@YT)OzU%fH-00Cf6-fMH;>O}OvApx7- literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-192x192.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..962223c6a57ec9c1ae0efa8319185c93193bcae0 GIT binary patch literal 12286 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-32x32.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..994f5b3fd4e48d979bf6c9ce42128fd82f38ff11 GIT binary patch literal 2216 zcmZ{lc{J4BAIHDOo+TlJ>1S;$c}z2movahWSQy&s?)lu$x$paWpU*k>o_jy3c4%{f6OtzY01&XW zFmqsKnd9K$V$ESuQ9l5HE!f}G)XpD=1Axf6+|l-c%WUGkA66AERaQB{6HJrSG38F< zX~tlh*k2}hh2_tVLEDFtDaa3WQRV9R0AphkBVe@C2ur&nB#IK^R`f-IRcB`7jIv9Cn~gL5tJAFBl>21>j0{1?i?-5x_|jn?xF!vg`IiZEvze+l5W+@sg^9H z8L|0FFw-S8*;?vy(Qef9L)$pU{#@>B?KI0&Dr(yVT-5Z)V_JffQ+=fn{u*|uui!p@ zI*qgwnBqP&Ij*Jp9A*!`mNGTf5R{`bwfWvXyKk_Cw!`&+(yircb8f9yy!JKi zSgAoekIpeiRZ21`O^F;!kJmD(c?|y3&#;RsR10=-F>@e1ds-mID;$s=DAM#`LEvH5 zLeXrI&ee^H^u0Iz-z6dGP^}L5nUxS9=F!pi?~*L}iVC-0T(!baMGj#7Xvy}p-3}ID zGyA=RgN||$ld@2WMla&9#7|`yMKDcTVdSZ#4?ck!kkK%rF@L7C)pae{U>e0Fkv+6p1 ziCw;E{z=OgJ?Xw-$(7Zt@~>?TqmOfyx%)2N2zvU)K%IU5MOhZ3ez~6MwCbJ8hSy^& zf5Ztlx~Ece7@d%ho+N+47pXT00M#Y1?L`~Mj%W``KqWK>X3yz{m8Cw|OH6e2YK4MY zE@$w_*;SVR{`$>IfA71bVt&EsfRdxBU`$L-NsMRCZ0ZG3f;Q)tenRtYq;2Vtj$cPs zAD>?%2(}9tODKQq!E{={83IS{d|boisq{M;igf3dCPK}q-{TY>uOw-@+N+U`LXQez zF(1sJNQGJF)J0ukC$RWj?0FAW8NKN?G-j(&&*}a20!({Sb&ZRuF$U1di37d@I@-ODn z%SbFksXdM!g5b+zjXOv@fh{s)3`2{!Q@5l$+XmEYR7C_`coNS#zO;*EG7YR9O&rRd zmuR%~TB^8=-T`t>n;ssynr#YCY5yK-y>oqFns0T&CRzMVX#|%1b$}cXvM`ewY$3!Z zxkl_}>q6K6I&#ZZSK+l~Rt;}t&G0~CWs9Q=+jawxmC-_o3vUXdUYeGeYLrf7^u}3H zn6|iLeWFyxD2z*az~40j+2Wj0g-4Qng92bLgRg$KX1)&+9L@|05ks!Y>)g~D>zQdv1dsCf zQ12|Uj?u&pCP_gpLD!3=J^MC#A3ZF`RC~yEL&_ogo5_-ub={J(@CDzqBr853Uip^Q7+#|FKz5y8!j?9_z25m_({Rl!p>ZPS-L-9}VZ6qthX*S2 z8je2i{dz(Mg;FTGy~_N#pz9|&Ij9$|qSC`F%?HsJZY!)B$st=4&GhQjvxKc<`XP=S zI-!W^1Hqwf`fpt?foq?K9oQVWtLFJK_&?YU?1(^v-%pKTpK>?ju&D;!P{N-aHhBXh z7rt4I^cG`~7vaBx;%yC5%(6R7qI_vC>pvItRNC}s77rV8q6*nZeFaz#aiUwcL_#IB zJ2DlbDCbil26>L>?`;KXX z30YRhoi~h)Jy?v03s9x?b~i7BLn{fOG>NuT>O`>5r`Pvisd`Z3eX-#iU*5#e-`u=6 zRvcJi^a8YY6xW~?S{aMdTIn*oPlLdx&U@Y_m~!pzTDLcPZQqj9T9pgf!=cY2 zpd+^Br2J3L=T7Cb{$(f$9O+4cN3#!xZaR2}S literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-96x96.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..d800b7222caf478e9e16bb4cf17479dbfd18c055 GIT binary patch literal 6038 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.gif b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.gif new file mode 100644 index 0000000000000000000000000000000000000000..73a65f842988bead7177adfab548329bdc835b6c GIT binary patch literal 6280 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 a-*bW!G0W_uYw<9>=l_9#{(pdf>-R5H)Y0<* literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.png b/packages/ep_theme/Resources/Private/Assets/images/icons/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..34c4504415b8cb24c532ff9707413f093a4b0a1f GIT binary patch literal 16602 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 + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/icon_select.png b/packages/ep_theme/Resources/Private/Assets/images/icons/icon_select.png new file mode 100644 index 0000000000000000000000000000000000000000..bbcbc23ff2b38db756d72586efce920a9a786051 GIT binary patch literal 918 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=hEVFiK~JM3hAM`dB6B=jtV<?;Zqle1Gx6p~WYGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&% zT$P<{nWAKG$7NGt1vDTxwIorYA~z?m*s8)-32d$vkPQ;nS5g2gDap1~itr6kaLzAE zRWQ{v)iY4C<5I9GN=dT{a&d!d2J&o`GD=Dctn~HE%ggo3jrH=2()A53EiLs8jP#9+ zbb%^#i!1X=5-W7`ij_e|K+JGSElw`VEGWs$&r<-InV6JcT4JlDqz|%0A8HHGx%zPT z0y%IWr{)2@Sp@Wtl-KH4z+h_1baoCX4sv%=@N{)HGqf}_(>2mFWMI&kSUT~%_TfaC z>99_>`&lvu6jmX4F z&6AA_6KiYt&7PGqfBE^(mA3Dn|E#H+cb@rLK-9?>rE(`yilYiHJ&)ti-}UyqMZrZM z^E8Xy|&i=uq&X#;tR*K5Z+QJ*n$R zi+}5Hlbs7btX=7@{U@nt)1i2^?1c;D*XSO4_e8(%Uy|z5)Rw1DOVTe@K7DTY%zMtq zH-F|F(fi0((&Y2#@L3b#AChSk0%x|rJbt=i&5DWdHJ_i~ptZB>j25d&n8zd~_xUR- zSISMFAz-Vg?0fLd|NHx1bt>jB-F2D$tAE?aYs}wtHAHzIf4*Sg<(^R8(w$>k|CPb* zo~wS?)_1i9xd(2hhIYR@@KbGz{R%Vy^Wi_gtbAS4~MCYz+n*s-u!zGQ(moK7KdKsF}vMGG; zFmS)Ob9=kzrB{-P_VKE|_r>lg`8}N{IQ^-d;P$7w%3V)&l~z496+HVyRq*W7a-};@ z!jbP0l+XkK^kaY+ literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_lineup.gif b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_lineup.gif new file mode 100644 index 0000000000000000000000000000000000000000..e6c57ec80f075556a5a26a70f235f48a49f75c8a GIT binary patch literal 172 zcmZ?wbhEHb6krfw_{_`j^5shz85vz&T?2jn($dnNo}O8=W-VK`Y}>YN$BrGle*OCQ z@821afZ|UURt5%M1|5(X$P5OS$PK2hqB|5$>R$f!V3I_Pc$3`8O71d6w>@>v3U@Tx zFC5#cU^S0_$4y-}R-O(GL#`aALz9XH=JADObL?IzR~`P!^?-)44#T=553-m;vJW#h QdZ@f$kIdZP&%j^}0DSvBF#rGn literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_slide.gif b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_domain_model_slide.gif new file mode 100644 index 0000000000000000000000000000000000000000..e6c57ec80f075556a5a26a70f235f48a49f75c8a GIT binary patch literal 172 zcmZ?wbhEHb6krfw_{_`j^5shz85vz&T?2jn($dnNo}O8=W-VK`Y}>YN$BrGle*OCQ z@821afZ|UURt5%M1|5(X$P5OS$PK2hqB|5$>R$f!V3I_Pc$3`8O71d6w>@>v3U@Tx zFC5#cU^S0_$4y-}R-O(GL#`aALz9XH=JADObL?IzR~`P!^?-)44#T=553-m;vJW#h QdZ@f$kIdZP&%j^}0DSvBF#rGn literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_half_and_two_quarters.png b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_half_and_two_quarters.png new file mode 100644 index 0000000000000000000000000000000000000000..a29ebeeae311be464a3d9cf9c57aace24911ddab GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NV3?%C=ER6$F`~f~8uI>dsn>KCQym|Bg|NqaN zIg>1Fu^uSJR1)MD%y7QeLr4MjvW@W pEp`X!S!j7wmMAd4=}bJp$Z(;`S=C19lr>N*gQu&X%Q~loCIFJxE7Je~ literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_halves.png b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_halves.png new file mode 100644 index 0000000000000000000000000000000000000000..bfdaf3a17edc50180cf278c17c6a318b65de9ac5 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NV3?%C=ER6$F`~f~8uI>dsn>KCQym|Bg|NqaN zIg>1Fu^uSJR1)MD%y7QeLr4MjvW@w o21lC~H%)P8H##s+f=z~jfw|Asl;8HMCr~GYr>mdKI;Vst09}|WumAu6 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_quarters.png b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_quarters.png new file mode 100644 index 0000000000000000000000000000000000000000..037522a13d18e55bcabe83a72cf29c1f2f914258 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NV3?%C=ER6$FLIFM@uI>dsn>KCQym|Bg|NqaN zIrH=9&qv?9Wr4CxB|(0{4CiY-gd~AHLr)jSkP60R2`2{+PDYW$Ca)6=dKo2j)qz$uKb76?HTfnGwne)Xd=N>gTe~DWM4fRVXf= literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_thirds.png b/packages/ep_theme/Resources/Private/Assets/images/icons/tx_eptheme_gridelements_thirds.png new file mode 100644 index 0000000000000000000000000000000000000000..f83afd3869497603fe43329776d0e8b3f125c5c7 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NV3?%C=ER6$F`~f~8uI>dsn>KCQym|Bg|NqaN zIg>1Fu^uSJR1)MD%y7QeLr4lphgByS3j3^P6dsn>KCQym|Bg|NqaN zIrH=9&qv?9Wr4CxB|(0{4CiY-gd~AHV^0^ykP60R2`2{+PDYW$Ca)6=dKo{jeF#rGn literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/icons_sprite.svg b/packages/ep_theme/Resources/Private/Assets/images/icons_sprite.svg new file mode 100644 index 0000000..1e817d6 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/icons_sprite.svg @@ -0,0 +1,85 @@ + diff --git a/packages/ep_theme/Resources/Private/Assets/images/lazy.png b/packages/ep_theme/Resources/Private/Assets/images/lazy.png new file mode 100644 index 0000000000000000000000000000000000000000..a84a4a8dab0a81d93f54aab15e6931e61e6cdb48 GIT binary patch literal 2423 zcmchZ`9BkmAIGPO&2pPoY;+J-*-~<)54rEVQEamhQOZ|LVs0xmDc9yMa+Nbwq;lnG zlw9Q==4P(g%=Ohj@p=F9emtH(yk773FR%AI#>&D_P~f-#000m)Hqx^J01kx#06Yv3 z@PHf=KHGoLh*oBH`uqF)tE;Qq+uLhvYbz@&0|NtWHhXJpYkhtF&!0bogM-V<%cG;C zLqkIh24i@5cxh>AWMpJ^c9z9rEi5cdO-(H>E>2EPPE1V9%*@Qq%}q~F&(F^@nauI= z@v*Tn4u`Y5ySulyx3RIYxw*Nsv%}?bo0_Qv008vKSWm|;z-6)1SQH>D0+7vJ%-cOR z*!UQD7?b^X&i|+igIzV&jQOaaf=2F3w2lMLW1BOdZC5L~(HZQ}=FHU5k)Dx)Hz=5m zc+MD**|TqyawN*8fWEO8sGh-kV{Yp;%GNkRgA$lGDn23{1oyDPpHoUBkD=wo?OEPz z!M$RPvwvz-4az_s$)@_+GrV6nSs#l?MfFz)h8C^}r!{?dq=58@ z#-C~*9@;P6?+ev#snbBHa#H&o3Q+tlZL{xvOE97VhLVwAa)Q*X-DBLU_eScdsq)4qy`~W8{S{x4XthATRZ>1@F z3m)L5BWCdx7I%?5PQ=c$W>3+#*~rCJpNps~Eed0*V~N@=#kIY^bJOI4DMYRnF8;PR zE|Ai`)|>u0aH#PQZ2hC=rUn?2NTBqBgYG^>9LUzF{x0}1#haKTQ(Vnk_JlYk5M=Mh zFD{D=zU@Rsz6?EK$8t*K3b{ahsfU6xFGQ$0dg-qR)Y6}0lLLOt@WFlDr*%~IYgGw zoy!eN4?>cZ;4jIyn)+c(+_=uGpiD$ZtvFweD8&#(xx0bk{SlQ18^%KToB1N*z4%eD%%0w&n4- zeNAim@y_85(nk#EyBWeu*-eSZAlLpib!t4=ti3SwXYrn22;CL}jFJ14(5#KZp#5gm zj&c$O0Zi_h#)lCNiwUf8TGRjDgkNi2)(CrdCYXnrpe@)W1Y-eScI^3;-QF>W;3lvz zOROKb?Gy-MKq9m$ej37hFSFzG6+)G;a7y32RV)Mh)J?6Pzxf0}D)^d#tB$G1<|Cj4 z@6Vh9VCAdFs{`=IMkms`_sd1EioA{t)pYlHb|j~!ZGuUyiomN2y?x|i%p`oCb;r%M>-9HzM3G=?iUZ1`QKn@^1xGrx`B^Ox7;-LLS_@Q@_f*E~iKs&wgGZzK_oR$CEbnt?=t6ev)H|$!6ou%LzU=*_vEJ5I zGvlA&LF9hs(l190AVgWkWPQ)UT`~CDw!K)E#Mhwyu%ih z`St3Hs952cY$v}RTHh_{HyYBdW#o2+5oJ^S9PQ{YsE`dwdh}aiMZNq<=IpPAJ2=&= zS+^JM%kBj&oz_dRVAHSENf^lYEW97`>|-dW_09P^Q(v2F(g5V*-K2_@w=iN6+r~e$ zS`SrJ3az!&i*q~czY-nLoLteCn4RXv!ga`-u-o0$IcRtD{B7nKRl8*_eQLNBwozcI zX8AYSJBh-5CJ}S!{*8X^aT`bvfnsweu#uWB)17Ms+JI5?2qRjC01ELiUO zyP;XW`qNI6dE|#^gG|f&d1M<$*3EQSqKWCgCspV+AWTO#?3^0Ded~QR1o5h`>pf41 iQq8{LfAldN*x&% \ No newline at end of file diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_boarderweek.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_boarderweek.svg new file mode 100644 index 0000000..64d3561 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_boarderweek.svg @@ -0,0 +1,673 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_ser.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_ser.svg new file mode 100644 index 0000000..05ab197 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_ser.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone.svg new file mode 100644 index 0000000..c693f6f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone.svg @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone_2019.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone_2019.svg new file mode 100644 index 0000000..7b534d9 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_snowzone_2019.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_uni_champ.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_uni_champ.svg new file mode 100644 index 0000000..e3c02fb --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_uni_champ.svg @@ -0,0 +1,1951 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Assets/images/logo_wide.svg b/packages/ep_theme/Resources/Private/Assets/images/logo_wide.svg new file mode 100644 index 0000000..596f534 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/images/logo_wide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/ep_theme/Resources/Private/Assets/images/low-poly.jpg b/packages/ep_theme/Resources/Private/Assets/images/low-poly.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dcf82cecb13cd876b4775be4f78eb3d53b2bf6f5 GIT binary patch literal 32480 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/polygon.jpg b/packages/ep_theme/Resources/Private/Assets/images/polygon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..09f17383ecf04c9b8421732c51171553c0dd416a GIT binary patch literal 25756 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 E1F%_CZ2$lO literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/images/pow_de.png b/packages/ep_theme/Resources/Private/Assets/images/pow_de.png new file mode 100644 index 0000000000000000000000000000000000000000..990709bda8a763bb2404c0f9ba4ba6f718f3b532 GIT binary patch literal 12382 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 otCjNt6@ss;gTR|6^+Z6j?8irQrQ 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 literal 0 HcmV?d00001 diff --git a/packages/ep_theme/Resources/Private/Assets/js/_app.js b/packages/ep_theme/Resources/Private/Assets/js/_app.js new file mode 100644 index 0000000..8802344 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/_app.js @@ -0,0 +1,46 @@ +// Polyfills +// import smoothscroll from 'smoothscroll-polyfill' +// smoothscroll.polyfill() + +// Add pacode to external links +document.addEventListener('DOMContentLoaded', () => { + const configElement = document.getElementById('appconfig') + if (null === configElement) { + return {} + } + const appConfig = JSON.parse(configElement.innerHTML) + const paCode = appConfig.paCode + const externalDomains = appConfig.externalDomains + if (paCode) { + document.querySelectorAll('a').forEach(element => { + if (element.hostname === location.hostname) { + return + } + if (externalDomains.indexOf(element.hostname) === -1) { + return + } + let url = element.href + if (!url) { + return + } + if (url.indexOf('agenturcode=') !== -1) { + return + } + const separator = url.indexOf('?') === -1 ? '?' : '&' + url += separator + 'agenturcode=' + paCode + element.href = url + }) + } +}, false) + +import { Application } from '@hotwired/stimulus' +import { definitionsFromContext } from '@hotwired/stimulus-webpack-helpers' + +const application = Application.start() +const context = require.context('./controllers', true, /\.js$/) +application.load(definitionsFromContext(context)) + +window.htmx = require('htmx.org') +htmx.config.includeIndicatorStyles = false +htmx.config.historyEnabled = false +htmx.config.allowScriptTags = false diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/autocollapse_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/autocollapse_controller.js new file mode 100644 index 0000000..a43c527 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/autocollapse_controller.js @@ -0,0 +1,54 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = [ 'sizer', 'container', 'button', 'bar' ] + + static values = { + offset: { + type: Number, + default: 0, + }, + height: { + type: Number, + default: 0, + }, + expanded: { + type: Boolean, + default: true, + } + } + + connect() { + // Determine height of element controlling max height if available + this.maxHeight = this.hasSizerTarget ? this.sizerTarget.offsetHeight : this.heightValue + + // Determine rendered height of element holding content + this.contentHeight = this.containerTarget.offsetHeight + + // Determine height of collapsed content element considering + // potential offset for button + this.collapsedHeight = this.maxHeight - this.offsetValue + + // Set initial expanded/collapsed status and button visibility + this.expandedValue = this.contentHeight < this.maxHeight + this.buttonTarget.classList.toggle('hidden', this.expandedValue) + + // Drop the space reserved for the toggle bar when the content fits anyway + if (this.expandedValue && this.hasBarTarget) { + this.barTarget.classList.add('hidden') + this.containerTarget.style.paddingBottom = '0' + } + } + + toggle() { + this.expandedValue = !this.expandedValue + } + + expandedValueChanged(expanded) { + this.containerTarget.style.height = expanded ? 'auto' : `${this.collapsedHeight}px` + this.containerTarget.style.overflowY = expanded ? 'none' : 'hidden' + this.buttonTarget.classList.toggle('toggle-more', false === expanded) + this.buttonTarget.classList.toggle('toggle-less', expanded) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/calendar_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/calendar_controller.js new file mode 100644 index 0000000..1e34b8e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/calendar_controller.js @@ -0,0 +1,68 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch } from '../mixins/use_fetch' + +export default class extends Controller { + + static targets = [ + 'container', + 'field', + 'loadingIndicator', + ] + + static values = { + loading: Boolean, + uri: String, + hotelUid: Number, + } + + initialize() { + useFetch(this) + } + + connect() { + let months = [] + let today = new Date() + let year = today.getFullYear() + let month = today.getMonth() + for (let i = 0; i < 20; i++) { + let selectableDate = new Date(year, month, 1) + months.push(selectableDate) + if (month < 12) { + month++ + } else { + month = 1 + year++ + } + this.fieldTarget.options[i] = new Option(selectableDate.toLocaleDateString('de-DE', {year: 'numeric', month: 'long'}), i.toString()) + } + this.selectableMonths = months + this.load(0) + } + + select(e) { + let selectedIndex = e.currentTarget.value + this.load(selectedIndex) + } + + load(selectedIndex) { + this.loadingValue = true + let selectedMonth = this.selectableMonths[selectedIndex] + let data = new URLSearchParams() + data.set('tx_epproducts_ajax[hotel]', this.hotelUidValue) + data.set('tx_epproducts_ajax[month]', selectedMonth.getMonth() + 1) + data.set('tx_epproducts_ajax[year]', selectedMonth.getFullYear()) + fetch(this.uriValue, { method: 'POST', body: data }) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => ( + this.containerTarget.innerHTML = html + )) + .finally(() => { + this.loadingValue = false + }) + } + + loadingValueChanged() { + this.loadingIndicatorTarget.classList.toggle('hidden', this.loadingValue === false) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/collapse_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/collapse_controller.js new file mode 100644 index 0000000..404a28d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/collapse_controller.js @@ -0,0 +1,58 @@ +import { Controller } from '@hotwired/stimulus' +import { useWindowResize, useDebounce, useClickOutside } from 'stimulus-use' + +export default class extends Controller { + + static debounces = [ 'checkBreakpoints' ] + + static values = { + collapsed: Boolean, + from: Number, + until: Number, + } + + static targets = [ 'container', 'icon' ] + + initialize() { + useWindowResize(this) + useDebounce(this) + useClickOutside(this) + } + + connect() { + this.toggleByWindowWidth(window.innerWidth) + } + + windowResize({ width, height, event }) { + this.toggleByWindowWidth(width) + } + + toggle() { + this.collapsedValue = !this.collapsedValue + } + + open() { + this.collapsedValue = false + } + + close() { + this.collapsedValue = true + } + + toggleByWindowWidth(width) { + if (0 !== this.untilValue) { + this.collapsedValue = width < this.untilValue + } + if (0 !== this.fromValue) { + this.collapsedValue = width > this.fromValue + } + } + + collapsedValueChanged() { + this.containerTarget.classList.toggle('hidden', this.collapsedValue === true) + this.containerTarget.scrollTop = 0 + if (this.hasIconTarget) { + this.iconTarget.classList.toggle('rotate-90', this.collapsedValue === false) + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/datalayer_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/datalayer_controller.js new file mode 100644 index 0000000..f71b5f8 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/datalayer_controller.js @@ -0,0 +1,14 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static values = { event: String } + + trigger() { + window.dataLayer = window.dataLayer || [] + dataLayer.push({ + 'event': this.eventValue, + }) + } + +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/datepicker_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/datepicker_controller.js new file mode 100644 index 0000000..c011c48 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/datepicker_controller.js @@ -0,0 +1,109 @@ +import { Controller } from '@hotwired/stimulus' +import { useDispatch } from 'stimulus-use' +import flatpickr from 'flatpickr' +import { German } from 'flatpickr/dist/l10n/de' + +export default class extends Controller { + + static targets = [ + 'field', + 'dateFrom', + 'dateTo', + ] + + static values = { + inline: Boolean, + minDate: String, + dateFrom: String, + dateTo: String, + enabledDates: Array, + } + + initialize() { + useDispatch(this) + const inline = this.hasInlineValue ? this.inlineValue : true + this.picker = flatpickr(this.fieldTarget, { + mode: 'range', + locale: German, + inline, + minDate: this.minDateValue, + altFormat: 'd.m.Y', + altInput: !inline, + onChange: selectedDates => { + if (selectedDates.length === 2) { + this.dateFromValue = flatpickr.formatDate(selectedDates[0], 'Y-m-d') + this.dateToValue = flatpickr.formatDate(selectedDates[1], 'Y-m-d') + this.update() + } + } + }) + if (this.hasEnabledDatesValue) { + let enabledDates = this.enabledDatesValue + let minDate = enabledDates[0] + let maxDate = enabledDates[enabledDates.length - 1] + if (this.hasMinDateValue && this.minDateValue > minDate) { + minDate = this.minDateValue + } + this.picker.set('enable', enabledDates) + this.picker.set('minDate', minDate) + this.picker.set('maxDate', maxDate) + this.picker.jumpToDate(minDate) + this.picker.set('onDayCreate', (dObj, dStr, fp, dayElem) => { + let day = flatpickr.formatDate(dayElem.dateObj, 'Y-m-d') + if (day >= minDate && -1 !== this.enabledDatesValue.indexOf(day)) { + dayElem.classList.add('available') + } + }) + } + } + + connect() { + if ('' !== this.dateFromValue && '' !== this.dateToValue) { + let dateFrom = new Date(this.dateFromValue) + let dateTo = new Date(this.dateToValue) + this.picker.setDate([ dateFrom, dateTo ]) + } + } + + selectPreset(e) { + e.preventDefault() + const preset = JSON.parse(e.currentTarget.dataset.preset) + this.dateFromValue = preset.dateFrom + this.dateToValue = preset.dateTo + let dateFrom = new Date(preset.dateFrom) + let dateTo = new Date(preset.dateTo) + this.picker.setDate([ dateFrom, dateTo ]) + this.update() + } + + reset(e) { + e.preventDefault() + this.picker.clear() + this.picker.jumpToDate() + this.dateFromValue = '' + this.dateToValue = '' + this.update() + } + + update() { + // Set values to form fields if applicable + if (this.hasDateFromTarget) { + this.dateFromTarget.value = this.dateFromValue + } + if (this.hasDateToTarget) { + this.dateToTarget.value = this.dateToValue + } + // Dispatch event with selected values and formatted range label + let label = '' + if (this.dateFromValue && this.dateToValue) { + let dateFrom = new Date(this.dateFromValue) + let dateTo = new Date(this.dateToValue) + label = `${flatpickr.formatDate(dateFrom, 'd.m.Y')}-${flatpickr.formatDate(dateTo, 'd.m.Y')}` + } + this.dispatch('range-select', { + from: this.dateFromValue, + to: this.dateToValue, + label + }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/daytrip_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/daytrip_controller.js new file mode 100644 index 0000000..092a3d8 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/daytrip_controller.js @@ -0,0 +1,62 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch } from '../mixins/use_fetch' + +export default class extends Controller { + + static targets = [ + 'container', + 'loadingIndicator', + 'message', + ] + + static values = { + loading: Boolean, + contingentsUri: String, + roomsUri: String, + hotelUid: Number, + productUid: Number, + dateFrom: String, + dateTo: String, + } + + initialize() { + useFetch(this) + } + + openModal({ params }) { + let eventName = `${params.type}-modal:open` + this.dispatch(eventName, { + detail: { + type: params.type, + uid: params.uid + } + }) + } + + load(e) { + this.loadingValue = true + this.dateFromValue = e.detail.from + this.dateToValue = e.detail.to + this.containerTarget.innerHTML = '' + let data = new FormData() + data.set('tx_epproducts_ajax[hotel]', this.hotelUidValue) + data.set('tx_epproducts_ajax[product]', this.productUidValue) + data.set('tx_epproducts_ajax[dateFrom]', this.dateFromValue) + data.set('tx_epproducts_ajax[dateTo]', this.dateToValue) + fetch(this.roomsUriValue, { method: 'POST', body: data }) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.containerTarget.innerHTML = html + this.messageTarget.classList.add('hidden') + }) + .finally(() => { + this.loadingValue = false + }) + } + + loadingValueChanged(isLoading) { + this.loadingIndicatorTarget.classList.toggle('hidden', false === isLoading) + this.loadingIndicatorTarget.classList.toggle('flex', true === isLoading) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/faq_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/faq_controller.js new file mode 100644 index 0000000..874d280 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/faq_controller.js @@ -0,0 +1,42 @@ +import { Controller } from '@hotwired/stimulus' +import { useDebounce } from 'stimulus-use' + +export default class extends Controller { + + static debounces = [ 'filter' ] + + static targets = [ 'entry', 'category', 'empty' ] + + initialize() { + useDebounce(this) + } + + filter(e) { + let value = e.target.value + if (value.length < 3) { + this.entryTargets.forEach(entry => entry.classList.remove('hidden')) + this.categoryTargets.forEach(category => category.classList.remove('hidden')) + this.toggleEmpty(false) + return + } + const pattern = new RegExp(value, 'i') + this.entryTargets.forEach(entry => { + const question = entry.querySelector('button span').textContent + const answer = entry.querySelector('[data-rte-content]').textContent + entry.classList.toggle('hidden', !question.match(pattern) && !answer.match(pattern)) + }) + this.categoryTargets.forEach(category => { + const hasVisible = Array.from(category.querySelectorAll('[data-faq-target="entry"]')) + .some(entry => !entry.classList.contains('hidden')) + category.classList.toggle('hidden', !hasVisible) + }) + const hasVisible = this.entryTargets.some(entry => !entry.classList.contains('hidden')) + this.toggleEmpty(!hasVisible) + } + + toggleEmpty(show) { + if (this.hasEmptyTarget) { + this.emptyTarget.classList.toggle('hidden', !show) + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/form_debounce_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/form_debounce_controller.js new file mode 100644 index 0000000..21474f2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/form_debounce_controller.js @@ -0,0 +1,25 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = ['button'] + static classes = [ 'disabled' ] + static values = { + isSubmitting: Boolean, + submittingLabel: { type: String, default: 'wird versendet' } + } + + debounce(e) { + if (true === this.isSubmittingValue) { + e.preventDefault() + e.stopPropagation() + return false + } + this.isSubmittingValue = true + this.buttonTarget.innerText = this.submittingLabelValue + if (this.hasDisabledClass) { + this.buttonTarget.classList.add(this.disabledClass) + } + } +} + diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/iframe_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/iframe_controller.js new file mode 100644 index 0000000..c6a592c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/iframe_controller.js @@ -0,0 +1,18 @@ +import { Controller } from '@hotwired/stimulus'; +import iframeResize from '@iframe-resizer/parent'; + +export default class extends Controller { + static values = { + license: { + type: String, + default: 'GPLv3', + } + } + + connect() { + iframeResize({ + license: this.licenseValue, + waitForLoad: true + }, this.element) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/lightbox_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/lightbox_controller.js new file mode 100644 index 0000000..780683c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/lightbox_controller.js @@ -0,0 +1,45 @@ +import { Controller } from '@hotwired/stimulus' +import Glightbox from 'glightbox' + +export default class extends Controller { + + static targets = [ 'thumb' ] + + static values = { url: String, height: String, type: String } + + connect() { + let elements = [] + let type = this.hasTypeValue ? this.typeValue : 'image' + if (this.hasUrlValue) { + let element = { href: this.urlValue, type } + if (this.hasHeightValue) { + element.height = this.heightValue + } + elements.push(element) + } else if (this.element.hasAttribute('href')) { + let element = { href: this.element.href, type } + elements.push(element) + } else { + this.thumbTargets.forEach(thumb => { + let element = { href: thumb.href, type } + let caption = this.element.hasAttribute('data-lightbox-caption') ? this.element.dataset.lightboxCaption : null + if (null !== caption) { + element.title = caption + } + elements.push(element) + }) + } + this.lightbox = Glightbox({ + elements + }) + } + + open(e) { + e.preventDefault() + let index = 1 + if (e.currentTarget.dataset.lightboxIndex) { + index = e.currentTarget.dataset.lightboxIndex + } + this.lightbox.openAt(index - 1) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js new file mode 100644 index 0000000..aea506f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/mobile_search_controller.js @@ -0,0 +1,80 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch} from '../mixins/use_fetch' + +export default class extends Controller { + + static targets = [ + 'form', + 'container', + 'loadingIndicator', + 'dateFrom', + 'dateTo', + ] + + static values = { + uri: String, + initUri: String, + loading: Boolean, + loaded: Boolean, + show: Boolean, + } + + initialize() { + useFetch(this) + } + + open(e) { + if (e.detail === 'mobile-search') { + this.showValue = true + if (false === this.loadedValue) { + this.load() + } + } + } + + close() { + this.showValue = false + } + + filter() { + this.load({ + method: 'POST', + body: new FormData(this.formTarget) + }) + } + + reset() { + this.load({ method: 'POST' }) + } + + load(options) { + let uri = this.loadedValue ? this.uriValue : this.initUriValue + this.loadingValue = true + fetch(uri, options) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.containerTarget.innerHTML = html + this.loadedValue = true + }) + .finally(() => { + this.loadingValue = false + }) + } + + selectDateRange(e) { + e.stopPropagation() + this.dateFromTarget.value = e.detail.from + this.dateToTarget.value = e.detail.to + this.filter() + } + + showValueChanged() { + this.element.classList.toggle('hidden', false === this.showValue) + } + + loadingValueChanged() { + this.containerTarget.classList.toggle('opacity-25', this.loadingValue === true) + this.loadingIndicatorTarget.classList.toggle('hidden', this.loadingValue === false) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/modal_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/modal_controller.js new file mode 100644 index 0000000..c3f2150 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/modal_controller.js @@ -0,0 +1,22 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = [ 'content' ] + + open({ detail: { uid } }) { + this.contentTargets.forEach(element => { + let elementUid = parseInt(element.dataset.uid) + element.classList.toggle('hidden', uid !== elementUid) + }) + this.element.classList.remove('hidden') + } + + close() { + this.element.classList.add('hidden', true) + } + + remove() { + this.element.remove() + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/nav_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/nav_controller.js new file mode 100644 index 0000000..e4bea2e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/nav_controller.js @@ -0,0 +1,36 @@ +import { Controller } from '@hotwired/stimulus' +import { useClickOutside } from 'stimulus-use' + +export default class extends Controller { + + static classes = ['hidden'] + static targets = ['button', 'subnav'] + static values = { expanded: Boolean } + + connect() { + useClickOutside(this) + } + + toggle(e) { + e.preventDefault() + this.expandedValue = !this.expandedValue + } + + clickOutside(e) { + this.expandedValue = false + } + + expandedValueChanged(expanded) { + if (this.hasButtonTarget) { + this.buttonTarget.setAttribute('aria-expanded', expanded) + } + if (this.hasSubnavTarget) { + this.subnavTarget.setAttribute('aria-hidden', !expanded) + if (true === expanded) { + this.subnavTarget.classList.remove(...this.hiddenClasses) + } else { + this.subnavTarget.classList.add(...this.hiddenClasses) + } + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/osm_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/osm_controller.js new file mode 100644 index 0000000..bb87d57 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/osm_controller.js @@ -0,0 +1,78 @@ +import { Controller } from '@hotwired/stimulus' +import L from 'leaflet' +import iconUrl from 'leaflet/dist/images/marker-icon.png' +import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png' +import shadowUrl from 'leaflet/dist/images/marker-shadow.png' + +// Icon.Default finds its images by reading the url() off .leaflet-default-icon-path and +// stripping a literal `marker-icon.png` off the end - which a content-hashed filename never +// matches, and the `link[href$="leaflet.css"]` fallback does not exist either because the +// CSS is inlined into the bundle. Dropping the _getIconUrl override falls back to plain +// option lookup, so the URLs Webpack emitted are used as they are. +delete L.Icon.Default.prototype._getIconUrl +L.Icon.Default.mergeOptions({ iconUrl, iconRetinaUrl, shadowUrl }) + +export default class extends Controller { + + static values = { + lat: Number, + lng: Number, + zoom: Number, + externalLink: String, + markers: Array, + tileUrl: String, + apiKey: String, + attribution: String, + } + + connect() { + this.map = L.map(this.element, { + scrollWheelZoom: false + }) + const zoom = this.hasZoomValue ? this.zoomValue : 12 + this.map.setZoom(zoom) + if (this.hasLatValue && this.hasLngValue) { + const position = L.latLng(this.latValue, this.lngValue) + const marker = L.marker(position) + if (this.hasExternalLinkValue && '' !== this.externalLinkValue) { + marker.on('click', () => { + window.open(this.externalLinkValue, '_blank') + }) + } else { + marker.on('click', () => { + window.open('https://www.google.com/maps/search/?api=1&zoom=15&query=' + this.latValue + ',' + this.lngValue, '_blank') + }) + } + marker.addTo(this.map) + this.map.setView(position) + } else if (this.hasMarkersValue && this.markersValue.length) { + const bounds = L.latLngBounds() + for (let item of this.markersValue) { + const position = L.latLng(item.lat, item.lng) + const marker = L.marker(position) + const popupContent = '

' + marker.bindPopup(popupContent) + marker.addTo(this.map) + bounds.extend(position) + } + this.map.fitBounds(bounds) + } + L.tileLayer(this.tileUrl(), { + maxZoom: 19, + // The site sends Referrer-Policy: same-origin, which strips the Referer on + // cross-origin requests. The tile provider needs it to match its key against + // the allowed-domain list, so widen the policy for tile requests only. + referrerPolicy: 'strict-origin-when-cross-origin', + attribution: this.attributionValue + }).addTo(this.map) + } + + tileUrl() { + if ('' === this.tileUrlValue || '' === this.apiKeyValue) { + return '' + } + const separator = -1 === this.tileUrlValue.indexOf('?') ? '?' : '&' + return this.tileUrlValue + separator + 'apiKey=' + encodeURIComponent(this.apiKeyValue) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/overlay_toggle_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/overlay_toggle_controller.js new file mode 100644 index 0000000..641187f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/overlay_toggle_controller.js @@ -0,0 +1,32 @@ +import { Controller } from '@hotwired/stimulus' +import { useDispatch } from 'stimulus-use' + +export default class extends Controller { + + static values = { + id: String, + show: Boolean + } + + static targets = [ 'overlay' ] + + initialize() { + useDispatch(this) + } + + invokeToggle() { + this.dispatch('toggle', this.idValue) + } + + toggle(e) { + if (e.detail === this.idValue) { + this.showValue = !this.showValue + } + } + + showValueChanged() { + if (this.hasOverlayTarget) { + this.overlayTarget.classList.toggle('hidden', this.showValue === false) + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/price_calendar_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/price_calendar_controller.js new file mode 100644 index 0000000..a0ee14c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/price_calendar_controller.js @@ -0,0 +1,185 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = ['mount', 'error', 'message', 'button', 'resetButton', 'day', 'month', 'prevButton', 'nextButton'] + + static values = { + hotelCode: String, + groupsBookingUrl: String, + } + + connect() { + this.startDate = null + this.endDate = null + this.currentIndex = 0 + + // mountTarget carries hx-get, so HTMX fires its events there; listening here + // scopes to this calendar instance and avoids catching events from other HTMX elements. + this.mountTarget.addEventListener('htmx:afterSwap', () => { + this.showMonths(0) + this.showButton() + }) + this.mountTarget.addEventListener('htmx:responseError', () => this.showError()) + } + + showMonths(startIndex) { + this.currentIndex = startIndex + this.monthTargets.forEach((m, i) => { + m.classList.toggle('hidden', i < startIndex || i >= startIndex + 2) + }) + if (this.hasPrevButtonTarget) this.prevButtonTarget.disabled = startIndex === 0 + if (this.hasNextButtonTarget) this.nextButtonTarget.disabled = startIndex >= this.monthTargets.length - 2 + } + + prevMonths() { + this.showMonths(Math.max(0, this.currentIndex - 1)) + } + + nextMonths() { + this.showMonths(Math.min(this.currentIndex + 1, this.monthTargets.length - 2)) + } + + selectDate(event) { + const btn = event.currentTarget + // cooltipz shows on :focus as well as :hover; blurring dismisses it after a click. + btn.blur() + const date = btn.dataset.date + const status = btn.dataset.status + + // All day buttons carry data-action for template simplicity; non-selectable ones are filtered here. + if (!status || status === 'blocked') return + + if (this.startDate && this.endDate) { + this.resetSelection() + } + + if (!this.startDate) { + // checkout-only days allow departure but not arrival + if (status === 'checkout-only') { + this.showMessage('Bitte wähle einen Anreisetag.') + return + } + this.startDate = date + this.endDate = null + this.updateDisplay() + this.showMessage('Bitte wähle das Abreisedatum.') + this.disableButton() + return + } + + if (date === this.startDate) { + this.resetSelection() + return + } + + // Normalize so from < to regardless of which date the user clicked first. + const [from, to] = date < this.startDate ? [date, this.startDate] : [this.startDate, date] + + if (this.hasBlockedInRange(from, to)) { + this.resetSelection() + this.showMessage('Dieser Zeitraum enthält nicht verfügbare Daten.') + return + } + + const startBtn = this.dayTargets.find(b => b.dataset.date === from) + const minNights = parseInt(startBtn?.dataset.minNights) || 0 + const nights = (new Date(to) - new Date(from)) / 86400000 // ms → days + + // Short-stay warning is advisory only; the booking link is still enabled below. + if (minNights > 0 && nights < minNights) { + this.showMessage(`Weniger als ${minNights} Nächte nur auf Anfrage.`) + } else { + this.hideMessage() + } + + this.startDate = from + this.endDate = to + this.updateDisplay() + this.enableButton(from, to) + this.dispatch('range-select', { detail: { dateFrom: from, dateTo: to } }) + } + + hasBlockedInRange(from, to) { + for (const btn of this.dayTargets) { + const d = btn.dataset.date + // End date is exclusive: guests may check out of a blocked day, so it is not a hard blocker. + if (d >= from && d < to && btn.dataset.status === 'blocked') return true + } + return false + } + + updateDisplay() { + for (const btn of this.dayTargets) { + const d = btn.dataset.date + btn.classList.remove('pc-day--pending-start', 'pc-day--selected-start', 'pc-day--selected-end', 'pc-day--in-range') + if (!this.startDate) continue + if (d === this.startDate) { + btn.classList.add(this.endDate ? 'pc-day--selected-start' : 'pc-day--pending-start') + } else if (this.endDate) { + if (d === this.endDate) btn.classList.add('pc-day--selected-end') + else if (d > this.startDate && d < this.endDate) btn.classList.add('pc-day--in-range') + } + } + } + + resetSelection() { + this.startDate = null + this.endDate = null + this.updateDisplay() + this.hideMessage() + this.disableButton() + } + + reset() { + this.resetSelection() + } + + showButton() { + if (this.hasButtonTarget) this.buttonTarget.classList.remove('hidden') + } + + enableButton(dateFrom, dateTo) { + if (!this.hasButtonTarget) return + const params = new URLSearchParams({ + hotel_code: this.hotelCodeValue, + date_from: dateFrom, + date_to: dateTo, + }) + this.buttonTarget.href = `${this.groupsBookingUrlValue}?${params}` + this.buttonTarget.textContent = `${this.formatDate(dateFrom)}–${this.formatDate(dateTo)} Anfragen / Buchen` + this.buttonTarget.classList.remove('opacity-50', 'cursor-not-allowed', 'pointer-events-none') + if (this.hasResetButtonTarget) this.resetButtonTarget.classList.remove('hidden') + } + + disableButton() { + if (!this.hasButtonTarget) return + this.buttonTarget.removeAttribute('href') + this.buttonTarget.textContent = 'Anfragen / Buchen' + this.buttonTarget.classList.add('opacity-50', 'cursor-not-allowed', 'pointer-events-none') + if (this.hasResetButtonTarget) this.resetButtonTarget.classList.add('hidden') + } + + formatDate(iso) { + const [y, m, d] = iso.split('-') + return `${d}.${m}.${y.slice(2)}` + } + + showMessage(text) { + if (this.hasMessageTarget) { + this.messageTarget.textContent = text + } + } + + hideMessage() { + if (this.hasMessageTarget) { + this.messageTarget.textContent = '' + } + } + + showError() { + if (this.hasErrorTarget) { + this.errorTarget.classList.remove('hidden') + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/pricetable_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/pricetable_controller.js new file mode 100644 index 0000000..a05952d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/pricetable_controller.js @@ -0,0 +1,106 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static targets = [ + 'row', + 'price', + 'busIcon', + 'surchargeToggleLabel', + 'discountToggleLabel', + 'rowToggleLabel', + 'rowToggleIcon', + ] + + static values = { + mode: String, + showAllRows: Boolean, + iconSpriteUrl: String, + } + + toggleSurcharge() { + this.modeValue = this.modeValue === 'surcharge' ? '' : 'surcharge' + } + + toggleDiscount() { + this.modeValue = this.modeValue === 'discount' ? '' : 'discount' + } + + toggleRows() { + this.showAllRowsValue = !this.showAllRowsValue + } + + openModal({ params }) { + let eventName = `${params.type}-modal:open` + this.dispatch(eventName, { + detail: { + type: params.type, + uid: params.uid + } + }) + } + + modeValueChanged() { + this.priceTargets.forEach(price => { + let room = parseFloat(price.dataset.room) + let bus = parseFloat(price.dataset.bus) + let discount = parseFloat(price.dataset.discount) + price.innerText = `${this.formatPrice(this.calculatePrice(room, bus, discount))} €` + }) + this.busIconTargets.forEach(icon => { + let busPrice = parseFloat(icon.dataset.busPrice) + let busIncluded = !!icon.dataset.busIncluded + icon.classList.toggle('hidden', this.isHideBusIcon(busPrice, busIncluded)) + }) + if (this.hasSurchargeToggleLabelTarget) { + let labelDefault = this.surchargeToggleLabelTarget.dataset.labelDefault + let labelAlt = this.surchargeToggleLabelTarget.dataset.labelAlt + this.surchargeToggleLabelTarget.innerText = this.modeValue === 'surcharge' ? labelDefault : labelAlt + } + if (this.hasDiscountToggleLabelTarget) { + let labelDefault = this.discountToggleLabelTarget.dataset.labelDefault + let labelAlt = this.discountToggleLabelTarget.dataset.labelAlt + this.discountToggleLabelTarget.innerText = this.modeValue === 'discount' ? labelDefault : labelAlt + } + } + + showAllRowsValueChanged() { + let additionalRows = this.rowTargets.slice(5) + if (additionalRows.length) { + additionalRows.forEach(row => { + row.classList.toggle('hidden', this.showAllRowsValue === false) + }) + } + if (this.hasRowToggleLabelTarget && this.hasRowToggleIconTarget) { + this.rowToggleLabelTarget.innerText = this.showAllRowsValue ? 'weniger Termine anzeigen' : 'alle Termine anzeigen' + this.rowToggleIconTarget.setAttribute('href', this.showAllRowsValue ? `${this.iconSpriteUrlValue}#icon-minus-circle` : `${this.iconSpriteUrlValue}#icon-plus-circle`) + } + } + + calculatePrice(minPrice, busPrice, discount) { + if ('surcharge' === this.modeValue) { + return minPrice + busPrice + } + if ('discount' === this.modeValue) { + return minPrice + discount + } + return minPrice + } + + // Keep in sync with EP\EpTheme\ViewHelpers\PriceViewHelper: whole euros + // as before ("229"), German decimals where there are cents ("99,90"). + formatPrice(amount) { + let cents = Math.round(amount * 100) + if (cents % 100 === 0) { + return String(cents / 100) + } + return (cents / 100).toFixed(2).replace('.', ',') + } + + isHideBusIcon(busPrice, busIncluded) { + return ( + busPrice && this.modeValue !== 'surcharge' || + busIncluded && this.modeValue === 'discount' + ) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/product_details_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/product_details_controller.js new file mode 100644 index 0000000..0c8b4d3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/product_details_controller.js @@ -0,0 +1,105 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch } from '../mixins/use_fetch' +import { useConfig } from '../mixins/use_config' + +export default class extends Controller { + + static targets = [ + 'loadingIndicator', + 'loadingMessage', + 'datesTableContainer', + 'priceTableContainer', + ] + + static values = { + datestableUri: String, + loading: Boolean, + loaded: Boolean, + mode: String, + disableTables: Boolean, + } + + initialize() { + useFetch(this) + useConfig(this) + } + + connect() { + if (false === this.disableTablesValue) { + this.loadDates() + } + } + + showDates() { + this.modeValue = 'dates' + } + + showPrices() { + this.modeValue = 'prices' + } + + switchView(e) { + let selectedView = e.detail + if ('dates' === selectedView) { + this.loadDates() + } + } + + loadDates() { + this.modeValue = 'dates' + if (true === this.loadedValue || true === this.disableTablesValue) { + return + } + this.loadingValue = true + let paCode = this.getConfig().paCode + const data = new URLSearchParams() + if (null !== paCode) { + data.append('pa', paCode) + } + this.datesTableContainerTarget.innerHTML = '' + fetch(`${this.datestableUriValue}&${data}`) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.datesTableContainerTarget.innerHTML = html + }).catch(() => { + }).finally(() => { + this.loadingValue = false + this.loadedValue = true + }) + } + + loadPrices({ params }) { + this.loadingValue = true + this.priceTableContainerTarget.innerHTML = '' + this.modeValue = 'prices' + let paCode = this.getConfig().paCode + const data = new URLSearchParams() + if (null !== paCode) { + data.append('pa', paCode) + } + fetch(`${params.pricesUrl}&${data}`) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.priceTableContainerTarget.innerHTML = html + }).catch(() => { + }).finally(() => { + this.loadingValue = false + }) + } + + loadingValueChanged() { + this.loadingMessageTarget.innerText = this.modeValue === 'prices' ? 'Lade Buchungsptionen...' : 'Lade Termine...' + this.loadingIndicatorTarget.classList.toggle('hidden', this.loadingValue === false) + } + + modeValueChanged() { + if (this.hasDatesTableContainerTarget) { + this.datesTableContainerTarget.classList.toggle('hidden', this.modeValue === 'prices') + } + if (this.hasPriceTableContainerTarget) { + this.priceTableContainerTarget.classList.toggle('hidden', this.modeValue === 'dates') + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_to_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_to_controller.js new file mode 100644 index 0000000..d6a5caa --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_to_controller.js @@ -0,0 +1,11 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static values = { target: String } + + scroll(e) { + e.preventDefault() + document.getElementById(this.targetValue).scrollIntoView({ behavior: 'smooth' }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_top_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_top_controller.js new file mode 100644 index 0000000..6f58730 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/scroll_top_controller.js @@ -0,0 +1,29 @@ +import { Controller } from '@hotwired/stimulus' +import { useDebounce } from 'stimulus-use' + +export default class extends Controller { + + static debounces = [ 'toggle' ] + static classes = [ 'hidden', 'visible' ] + + initialize() { + useDebounce(this) + } + + connect() { + window.addEventListener('scroll', () => { + this.toggle() + }, { passive: true }) + this.toggle() + } + + toggle() { + let isVisible = window.scrollY >= window.innerHeight / 3 + this.element.classList.toggle(this.hiddenClass, false === isVisible) + this.element.classList.toggle(this.visibleClass, isVisible) + } + + scroll() { + window.scroll({ top: 0, left: 0, behavior: 'smooth' }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/search_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/search_controller.js new file mode 100644 index 0000000..8132fb1 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/search_controller.js @@ -0,0 +1,59 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch } from '../mixins/use_fetch' + +export default class extends Controller { + + static values = { + loading: Boolean, + uri: String, + } + + static targets = [ + 'form', + 'resultsContainer', + 'loadingIndicator', + 'dateFrom', + 'dateTo', + ] + + initialize() { + useFetch(this) + } + + filter() { + this.load({ + method: 'POST', + body: new FormData(this.formTarget) + }) + } + + reset() { + this.load({ method: 'POST' }) + } + + load(options) { + this.loadingValue = true + fetch(this.uriValue, options) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.resultsContainerTarget.innerHTML = html + window.scroll({ top: 0, left: 0, behavior: 'smooth' }) + htmx.process(this.resultsContainerTarget) + }) + .finally(() => { + this.loadingValue = false + }) + } + + selectDateRange(e) { + this.dateFromTarget.value = e.detail.from + this.dateToTarget.value = e.detail.to + this.filter() + } + + loadingValueChanged() { + this.resultsContainerTarget.classList.toggle('opacity-25', this.loadingValue === true) + this.loadingIndicatorTarget.classList.toggle('hidden', this.loadingValue === false) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_controller.js new file mode 100644 index 0000000..0c182a6 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_controller.js @@ -0,0 +1,82 @@ +import { Controller } from '@hotwired/stimulus' +import { useDispatch } from 'stimulus-use' + +export default class extends Controller { + + static targets = [ + 'container', + 'dateFrom', + 'dateTo', + 'destinationUid', + 'destinationType', + 'pax', + 'priceRange', + 'dateRangeLabel', + 'paxLabel', + 'destinationLabel', + 'priceRangeLabel', + ] + + static values = { collapsed: Boolean } + + initialize() { + useDispatch(this) + } + + setDateRange(e) { + this.dateRangeLabelTarget.value = e.detail.label + this.dateFromTarget.value = e.detail.from + this.dateToTarget.value = e.detail.to + this.dispatch('input', this.getDetails()) + } + + setPax(e) { + e.preventDefault() + let pax = e.currentTarget.dataset.option + this.paxTarget.value = pax + this.paxLabelTarget.value = pax + this.dispatch('input', this.getDetails()) + } + + setDestination(e) { + e.preventDefault() + this.destinationUidTarget.value = e.currentTarget.dataset.optionUid + this.destinationTypeTarget.value = e.currentTarget.dataset.optionType + this.destinationLabelTarget.value = e.currentTarget.dataset.optionLabel + this.dispatch('input', this.getDetails()) + } + + resetDestination(e) { + e.preventDefault() + this.destinationUidTarget.value = '' + this.destinationTypeTarget.value = '' + this.destinationLabelTarget.value = '' + this.dispatch('input', this.getDetails()) + } + + setPriceRange(e) { + e.preventDefault() + this.priceRangeTarget.value = e.currentTarget.dataset.optionValue + this.priceRangeLabelTarget.value = e.currentTarget.dataset.labelValue + this.dispatch('input', this.getDetails()) + } + + getDetails() { + return { + dateFrom: this.dateFromTarget.value, + dateTo: this.dateToTarget.value, + pax: this.paxTarget.value, + destinationUid: this.destinationUidTarget.value, + destinationType: this.destinationTypeTarget.value, + priceRange: this.priceRangeTarget.value, + } + } + + toggle() { + this.collapsedValue = !this.collapsedValue + } + + collapsedValueChanged() { + this.containerTarget.classList.toggle('hidden', this.collapsedValue === true) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_toggle_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_toggle_controller.js new file mode 100644 index 0000000..623bca8 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/searchbar_toggle_controller.js @@ -0,0 +1,14 @@ +import { Controller } from '@hotwired/stimulus' +import { useDispatch } from 'stimulus-use' + +export default class extends Controller { + + initialize() { + useDispatch(this) + } + + toggle(e) { + e.preventDefault() + this.dispatch('toggle') + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/slider_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/slider_controller.js new file mode 100644 index 0000000..bcdfc3d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/slider_controller.js @@ -0,0 +1,106 @@ +import { Controller } from '@hotwired/stimulus' +import Splide from '@splidejs/splide' + +const defaultOptions = { + arrows: false, + pagination: false, + perMove: 1, + gap: '1rem', + pauseOnHover: false, + pauseOnFocus: false, +} + +const presets = { + header: { + ...defaultOptions, + autoplay: true, + type: 'fade', + perPage: 1, + gap: 0, + rewind: true, + }, + content: { + ...defaultOptions, + rewind: true, + pagination: true, + arrows: true, + gap: 0, + classes: { + pagination: 'slider-pagination', + page: 'slider-pagination__dot', + }, + breakpoints: { + 768: { + pagination: false, + }, + }, + }, + teasers: { + ...defaultOptions, + perPage: 4, + padding: { + right: '2rem', + }, + breakpoints: { + 1200: { + perPage: 4, + }, + 1024: { + perPage: 3, + }, + 960: { + perPage: 2, + }, + 640: { + perPage: 1, + }, + }, + }, + teasersEmbedded: { + ...defaultOptions, + perPage: 2, + padding: { + right: '25%', + }, + breakpoints: { + 960: { + perPage: 3, + padding: { + right: '10%', + }, + }, + 768: { + perPage: 2, + padding: { + right: '25%', + }, + }, + 640: { + perPage: 2, + padding: { + right: '10%', + }, + }, + 512: { + perPage: 1, + padding: { + right: '10%', + }, + }, + }, + }, +} + +export default class extends Controller { + + static values = { preset: String } + + connect() { + let options = {} + let preset = this.presetValue + if (preset in presets) { + options = presets[preset] + } + new Splide(this.element, options).mount() + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/sticky_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/sticky_controller.js new file mode 100644 index 0000000..187af84 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/sticky_controller.js @@ -0,0 +1,54 @@ +import { Controller } from '@hotwired/stimulus' +import { useDebounce } from 'stimulus-use' + +export default class extends Controller { + + static debounces = [ 'toggle' ] + + static values = { + sticky: Boolean, + classes: String, + offset: Number, + padding: String, + disabled: Boolean, + } + + initialize() { + useDebounce(this) + } + + connect() { + window.addEventListener('scroll', () => { + this.toggle() + }, { passive: true }) + } + + disconnect() { + window.removeEventListener('scroll', () => { + this.toggle() + }, { passive: true }) + } + + toggle() { + if (true === this.disabledValue) { + return + } + this.stickyValue = window.scrollY > this.offsetValue + } + + stickyValueChanged(value) { + if (this.hasPaddingValue) { + if (true === value) { + document.body.style.paddingTop = this.paddingValue + } else { + document.body.removeAttribute('style') + } + } + if (!this.hasClassesValue) { + return + } + this.classesValue.split(' ').forEach(className => { + this.element.classList.toggle(className, this.stickyValue) + }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/tabs_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/tabs_controller.js new file mode 100644 index 0000000..0c620f3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/tabs_controller.js @@ -0,0 +1,34 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static classes = [ 'button', 'buttonActive' ] + + static targets = [ 'button', 'tab' ] + + static values = { activeTab: String } + + connect() { + this.activeTabValue = this.tabTargets[0].dataset.tab + } + + select(e) { + let selectedTab = e.currentTarget.dataset.tab + this.activeTabValue = selectedTab + this.dispatch('selected', { detail: selectedTab }) + this.dispatch('tab-' + selectedTab) + } + + activeTabValueChanged() { + let activeTab = this.activeTabValue + this.tabTargets.forEach(element => { + let currentTab = element.dataset.tab + element.classList.toggle('hidden', activeTab !== currentTab) + }) + this.buttonTargets.forEach(element => { + let currentButton = element.dataset.tab + element.classList.toggle(this.buttonClass, activeTab !== currentButton) + element.classList.toggle(this.buttonActiveClass, activeTab === currentButton) + }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/teasergrid_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/teasergrid_controller.js new file mode 100644 index 0000000..c58a0ec --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/teasergrid_controller.js @@ -0,0 +1,37 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static classes = [ 'grid' ] + + static targets = [ + 'button', + 'container', + ] + + static values = { disabled: Boolean } + + grid(e) { + e.preventDefault() + this.disabledValue = false + this.toggleButtonClass(e) + } + + block(e) { + e.preventDefault() + this.disabledValue = true + this.toggleButtonClass(e) + } + + toggleButtonClass(e) { + this.buttonTargets.forEach(element => { + element.classList.toggle('opacity-50', element === e.currentTarget) + }) + } + + disabledValueChanged() { + if (this.hasContainerTarget) { + this.containerTarget.classList.toggle(this.gridClass, this.disabledValue === false) + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_controller.js new file mode 100644 index 0000000..5999523 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_controller.js @@ -0,0 +1,37 @@ +import { Controller } from '@hotwired/stimulus' +import { useFetch } from '../mixins/use_fetch' + +export default class extends Controller { + + static targets = [ 'container', 'loadingIndicator' ] + + static values = { + uri: String, + loading: Boolean, + } + + initialize() { + useFetch(this) + } + + connect() { + this.load() + } + + load() { + this.loadingValue = true + let data = new URLSearchParams({ + 'tx_epproducts_ajax[watchlistUids]': sessionStorage.getItem('myep-watchlist'), + }) + fetch(this.uriValue, { method: 'POST', body: data }) + .then(this.checkStatus) + .then(this.parseHTML) + .then(html => { + this.containerTarget.innerHTML = html + htmx.process(this.containerTarget) + }) + .finally(() => { + this.loadingValue = false + }) + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_label_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_label_controller.js new file mode 100644 index 0000000..23bf909 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_label_controller.js @@ -0,0 +1,30 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + + static values = { count: Number } + + static targets = [ 'label', 'icon' ] + + connect() { + this.watchlist = JSON.parse(sessionStorage.getItem('myep-watchlist')) || [] + this.countValue = this.watchlist.length + } + + update(e) { + let key = e.detail.key + let index = this.watchlist.indexOf(key) + if (-1 !== index) { + this.watchlist.splice(index, 1) + } else { + this.watchlist.push(key) + } + this.countValue = this.watchlist.length + sessionStorage.setItem('myep-watchlist', JSON.stringify(this.watchlist)) + } + + countValueChanged() { + this.iconTarget.setAttribute('fill', this.countValue > 0 ? 'currentColor' : 'none') + this.labelTarget.innerText = `Merkliste (${this.countValue})` + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_toggle_controller.js b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_toggle_controller.js new file mode 100644 index 0000000..5ae5e97 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/controllers/watchlist_toggle_controller.js @@ -0,0 +1,45 @@ +import { Controller } from '@hotwired/stimulus' +import { useDispatch } from 'stimulus-use' + +export default class extends Controller { + + static values = { + key: String, + onList: Boolean + } + + static targets = [ + 'label', + 'icon', + 'ariaLabel', + ] + + initialize() { + useDispatch(this) + } + + connect() { + let watchlist = JSON.parse(sessionStorage.getItem('myep-watchlist')) || [] + this.onListValue = -1 !== watchlist.indexOf(this.keyValue) + } + + toggle() { + this.onListValue = !this.onListValue + this.dispatch('toggle', { + key: this.keyValue, + onList: this.onListValue, + }) + } + + onListValueChanged() { + if (this.hasLabelTarget) { + this.labelTarget.innerText = true === this.onListValue ? 'von der Merkliste entfernen' : 'auf die Merkliste setzen' + } + if (this.hasIconTarget) { + this.iconTarget.setAttribute('fill', true === this.onListValue ? 'currentColor' : 'none') + } + if (this.hasAriaLabelTarget) { + this.ariaLabelTarget.setAttribute('aria-label', true === this.onListValue ? 'von der Merkliste entfernen' : 'auf die Merkliste setzen') + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/main.js b/packages/ep_theme/Resources/Private/Assets/js/main.js new file mode 100644 index 0000000..7982173 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/main.js @@ -0,0 +1,5 @@ +// Main app +require('./_app'); + +// Main styles +require('../scss/main.scss'); diff --git a/packages/ep_theme/Resources/Private/Assets/js/mixins/use_config.js b/packages/ep_theme/Resources/Private/Assets/js/mixins/use_config.js new file mode 100644 index 0000000..acef946 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/mixins/use_config.js @@ -0,0 +1,11 @@ +export const useConfig = controller => { + Object.assign(controller, { + getConfig() { + const configElement = document.getElementById('appconfig') + if (null === configElement) { + return {} + } + return JSON.parse(configElement.innerHTML) + } + }) +} diff --git a/packages/ep_theme/Resources/Private/Assets/js/mixins/use_fetch.js b/packages/ep_theme/Resources/Private/Assets/js/mixins/use_fetch.js new file mode 100644 index 0000000..904da32 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/js/mixins/use_fetch.js @@ -0,0 +1,19 @@ +export const useFetch = controller => { + Object.assign(controller, { + checkStatus(response) { + if (response.status >= 200 && response.status < 300) { + return response + } else { + const error = new Error(response.statusText) + error.response = response + throw error + } + }, + parseJSON(response) { + return response.json() + }, + parseHTML(response) { + return response.text() + } + }) +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/_base.scss b/packages/ep_theme/Resources/Private/Assets/scss/_base.scss new file mode 100644 index 0000000..72910bf --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/_base.scss @@ -0,0 +1,15 @@ +@use "sass:meta"; +@layer base { + body { + padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); + } + @include meta.load-css("base/typography"); + @include meta.load-css("base/images"); + + @include meta.load-css("base/rte-content"); + + iframe { + width: 100%; + height: 100vh; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/_components.scss b/packages/ep_theme/Resources/Private/Assets/scss/_components.scss new file mode 100644 index 0000000..107d601 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/_components.scss @@ -0,0 +1,15 @@ +@use "sass:meta"; +@layer components { + @include meta.load-css("components/topnav"); + @include meta.load-css("components/background"); + @include meta.load-css("components/badges"); + @include meta.load-css("components/buttons"); + @include meta.load-css("components/contact-bar"); + @include meta.load-css("components/calendar"); + @include meta.load-css("components/forms"); + @include meta.load-css("components/herounit"); + @include meta.load-css("components/misc"); + @include meta.load-css("components/searchbar"); + @include meta.load-css("components/teaser"); + @include meta.load-css("components/travelinfo"); +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/_utilities.scss b/packages/ep_theme/Resources/Private/Assets/scss/_utilities.scss new file mode 100644 index 0000000..f1293f5 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/_utilities.scss @@ -0,0 +1,40 @@ +@layer utilities { + .max-w-screen-2xl { + max-width: 1440px; + } + .max-w-1\/2 { + max-width: 50%; + } + .has-validation-error input { + @apply text-red-500 border-red-500; + } + .container-wide { + @apply container; + max-width: 1440px; + } + .underlined { + @apply relative mb-4; + + &:after { + content: ""; + @apply absolute top-100 left-0 mt-2 mb-2 border-b-4 w-48; + @apply border-ep-primary-dark sbw:border-sbw-primary-dark snz:border-snz-primary-dark suz:border-suz-primary-dark uch:border-uch-primary-dark; + } + } + .-translate-xy-1\/2 { + transform: translate(-50%, -50%); + } + .text-shadow { + text-shadow: 0 2px 2px theme('colors.zinc.400'); + } + .hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + border: 0; + } + + iframe:not([src]) { + display: none; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/_vendor.scss b/packages/ep_theme/Resources/Private/Assets/scss/_vendor.scss new file mode 100644 index 0000000..3b6656e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/_vendor.scss @@ -0,0 +1,143 @@ +@use "sass:meta"; + +// Lightbox +@import "~glightbox/dist/css/glightbox.css"; + +// Tooltips +@include meta.load-css("cooltipz-css/cooltipz.css"); + +:root { + --cooltipz-bg-color: theme('colors.zinc.700'); + --cooltipz-border-color: theme('colors.zinc.700'); + --cooltipz-text-color: theme('colors.white'); +} + +// Datepicker +@import "~flatpickr/dist/flatpickr.css"; + +[data-datepicker-inline-value="true"] .flatpickr-input { + @apply hidden; +} + +.flatpickr-calendar { + border: none #{!important}; + + &.inline { + @apply shadow-none #{!important}; + @apply border border-zinc-400 #{!important}; + } +} + +.flatpickr-day { + @apply rounded my-2 mx-0; + + &.available:not(.inRange) { + @apply text-white bg-emerald-500; + } +} + +.flatpickr-day.selected { + @apply bg-ep-secondary sbw:bg-sbw-secondary; + @apply border-ep-secondary sbw:border-sbw-secondary; + + &.startRange, &.endRange { + @apply bg-ep-secondary sbw:bg-sbw-secondary; + @apply border-ep-secondary sbw:border-sbw-secondary; + } +} + +// Price calendar (hand-rolled) +.pc-loading-indicator { + display: none; + + &.htmx-request { + display: flex; + } +} + +.pc-day { + @apply relative w-full text-center py-1 px-0.5 text-sm leading-tight rounded text-white cursor-pointer; + + // Status colors — [data-status] attribute adds specificity (0,2,0) + &[data-status="ok"] { + @apply bg-emerald-500; + } + &[data-status="blocked-to-ok"] { + background: linear-gradient(to bottom right, theme('colors.gray.400') 50%, theme('colors.emerald.500') 50%); + } + &[data-status="checkout-only"] { + background: linear-gradient(to bottom right, theme('colors.emerald.500') 50%, theme('colors.gray.400') 50%); + } + &[data-status="blocked"] { + @apply bg-gray-400 cursor-default; + } + &[data-status=""], &:not([data-status]) { + @apply text-gray-400 cursor-default; + } + + // Compound selector gives (0,2,0) specificity, matching [data-status] rules; source order wins. + &.pc-day--pending-start, + &.pc-day--selected-start, + &.pc-day--selected-end { + @apply bg-none outline-none bg-ep-secondary text-gray-700; + } + + &.pc-day--in-range { + @apply bg-none bg-ep-secondary text-gray-700 opacity-55; + } +} + +// Maps +@import "~leaflet/dist/leaflet.css"; + +.leaflet-pane { + z-index: 10 !important; +} +.leaflet-top, .leaflet-bottom, .leaflet-control { + z-index: 20 !important; +} + +// Slider +@import "~@splidejs/splide/dist/css/splide-core.min.css"; + +.slider-pagination { + @apply absolute bottom-0 left-1/2 transform -translate-x-1/2 md:mb-8; + @apply list-none p-0; + @apply flex items-center space-x-2; +} + +.slider-pagination__dot { + @apply hide-text block w-3 h-3 xl:w-4 xl:h-4 rounded-full bg-white shadow-md focus:outline-none; + + &.is-active { + @apply bg-ep-secondary; + } +} + +.slider-pagination__arrow { + @apply absolute z-10 text-ep-secondary bg-transparent focus:outline-none; + + .slider--teasers & { + @apply bg-ep-secondary text-white rounded p-1; + } + + &[disabled] { + @apply opacity-25; + } + + &--prev { + @apply left-0 top-1/2 transform -translate-y-1/2 ml-4; + + .slider--teasers & { + @apply -ml-8; + } + } + + &--next { + @apply right-0 top-1/2 transform -translate-y-1/2 mr-4; + + .slider--teasers & { + @apply -mr-8; + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/base/_fonts.scss b/packages/ep_theme/Resources/Private/Assets/scss/base/_fonts.scss new file mode 100644 index 0000000..b055ca2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/base/_fonts.scss @@ -0,0 +1,43 @@ +/* lato-300 - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: local(''), + url('../../fonts/lato-v17-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../../fonts/lato-v17-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +/* lato-regular - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local(''), + url('../../fonts/lato-v17-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../../fonts/lato-v17-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +/* lato-700 - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: local(''), + url('../../fonts/lato-v17-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../../fonts/lato-v17-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +/* lato-900 - latin */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: local(''), + url('../../fonts/lato-v17-latin-900.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../../fonts/lato-v17-latin-900.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/base/_images.scss b/packages/ep_theme/Resources/Private/Assets/scss/base/_images.scss new file mode 100644 index 0000000..df2c609 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/base/_images.scss @@ -0,0 +1,5 @@ +.scale, +.img-responsive { + @apply block w-full h-auto; +} + diff --git a/packages/ep_theme/Resources/Private/Assets/scss/base/_rte-content.scss b/packages/ep_theme/Resources/Private/Assets/scss/base/_rte-content.scss new file mode 100644 index 0000000..e91946a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/base/_rte-content.scss @@ -0,0 +1,86 @@ +// Self-contained: the wrapper lives here so `&` and @extend resolve the same +// way they did when this file was @imported into a [data-rte-content] block. +[data-rte-content] { + h1, + .headline--primary, + h2, + .headline--secondary, + h3, + .headline--3, + h4, + .headline--4 { + @apply mb-4; + } + + ul { + @apply list-disc pl-4 mb-4; + } + + ol { + @apply list-decimal pl-4 mb-4; + } + + p { + @apply mb-4; + } + + a:not(.button):not(.rte-button):not(.rte-button--full) { + @apply underline text-ep-primary-dark sbw:text-sbw-primary-dark; + + .bg-brand-gradient & { + @apply text-white underline; + } + } + + a.rte-button { + @apply inline-flex items-center justify-center px-4 py-2 md:px-8; + @apply uppercase leading-none text-center; + @apply transition-colors outline-none; + @apply bg-ep-primary-light text-white; + @apply sbw:bg-sbw-secondary; + @apply snz:bg-snz-secondary; + @apply suz:bg-suz-secondary; + @apply uch:bg-uch-secondary; + @apply ser:bg-ser-secondary; + @apply hover:bg-ep-secondary group-hover:bg-ep-secondary; + @apply sbw:hover:bg-sbw-primary-dark sbw:group-hover:bg-sbw-primary-dark; + @apply snz:hover:bg-snz-primary-dark snz:group-hover:bg-snz-primary-dark; + @apply suz:hover:bg-suz-primary-dark suz:group-hover:bg-suz-primary-dark; + @apply uch:hover:bg-uch-primary-dark uch:group-hover:bg-uch-primary-dark; + @apply ser:hover:bg-ser-primary ser:group-hover:bg-ser-primary; + @apply focus:outline focus:outline-2 focus:outline-offset-2; + @apply focus:outline-ep-primary-dark sbw:focus:outline-sbw-secondary uch:focus:outline-uch-secondary; + @apply suz:focus:outline-suz-secondary ser:focus:outline-ser-secondary snz:focus:outline-snz-secondary; + + } + + a.rte-button--full { + @extend .rte-button; + @apply w-full; + } + + table, + .table { + @apply w-full; + } + + thead { + tr { + @apply bg-zinc-200; + } + } + + tbody { + tr { + @apply odd:bg-zinc-100; + } + } + + th { + @apply border-r border-white/50 px-2 py-1 md:p-2 text-left text-sm md:text-base; + } + + td { + @apply border-r border-zinc-200 px-2 py-1 md:p-2; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/base/_typography.scss b/packages/ep_theme/Resources/Private/Assets/scss/base/_typography.scss new file mode 100644 index 0000000..323f077 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/base/_typography.scss @@ -0,0 +1,49 @@ +h1, +.headline--primary { + @apply block text-3xl md:text-4xl font-bold mb-2; +} + +h2, +.headline--secondary { + @apply block text-2xl md:text-3xl font-bold mb-2; +} + +h3, +.headline--3 { + @apply block text-xl md:text-2xl font-bold leading-6 mb-2; +} + +h4, +.headline--4 { + @apply block md:text-lg font-bold; +} + +.headline--underlined:after { + @apply block border-b-4 border-ep-primary-light pb-4 mb-4 -mt-2 w-20; + @apply sbw:border-sbw-primary snz:border-snz-primary suz:border-suz-primary uch:border-uch-primary ser:border-ser-secondary; + content: ''; +} + +.subline { + @apply block text-sm md:text-base font-normal leading-6; +} + +// Lists +.check-list { + @apply list-none p-0; + + li { + @apply relative pl-8; + &:before { + @apply absolute left-0 top-0 w-6 h-6 text-ep-primary-dark text-lg; + font-family: 'fontello', sans-serif; + content: '\e800'; + } + } + + &--inverted { + li:before { + content: '\e802'; + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_background.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_background.scss new file mode 100644 index 0000000..b1e9485 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_background.scss @@ -0,0 +1,21 @@ +.bg-winter { + @apply bg-cover bg-no-repeat bg-center; + background-image: url(../../images/background/winter.jpg); +} + +.bg-pattern { + @apply bg-cover bg-no-repeat bg-center; + background-image: url(../../images/background/pattern.jpg); +} + +.bg-header-season-w { + @screen md { + background-image:url('../../images/background/header-winter.jpg'); + } +} + +.bg-header-season-s { + @screen md { + background-image: url('../../images/background/header-sommer.jpg'); + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_badges.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_badges.scss new file mode 100644 index 0000000..03c10cf --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_badges.scss @@ -0,0 +1,51 @@ +.concept-badge { + @apply w-full h-8 text-white px-4 leading-none uppercase bg-concept-default; + + &--bdg { + @apply bg-concept-bdg; + } + + &--skr { + @apply bg-concept-skr; + } + + &--evt { + @apply bg-concept-evt; + } + + &--fam { + @apply bg-concept-fam; + } + + &--grp { + @apply bg-concept-grp; + } + + &--prg { + @apply bg-concept-prg; + } + + &--ind { + @apply bg-concept-ind; + } + + &--wkd { + @apply bg-concept-wkd; + } + + &--kt { + @apply bg-concept-kt; + } + + &--spz { + @apply bg-concept-spz; + } + + &--epe { + @apply bg-concept-epe; + } + + &--sfl { + @apply bg-concept-sfl; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_buttons.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_buttons.scss new file mode 100644 index 0000000..b64c6aa --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_buttons.scss @@ -0,0 +1,100 @@ +.button { + @apply inline-flex items-center justify-center space-x-2 px-4 py-2 md:px-8 cursor-pointer; + @apply uppercase leading-none text-center; + @apply transition-colors outline-none; + @apply focus:outline focus:outline-2 focus:outline-offset-2; + @apply focus:outline-ep-primary-dark sbw:focus:outline-sbw-secondary uch:focus:outline-uch-secondary; + @apply suz:focus:outline-suz-secondary ser:focus:outline-ser-secondary snz:focus:outline-snz-secondary; + + &--small { + @apply px-2 py-1 md:px-4 text-xs sm:text-sm; + } + + &--full { + @apply block w-full; + } +} + +.teaser-button { + @apply w-full; + @apply bg-ep-primary-dark text-white; + @apply sbw:bg-sbw-secondary; + @apply snz:bg-snz-secondary; + @apply suz:bg-suz-secondary; + @apply uch:bg-uch-secondary; + @apply ser:bg-ser-secondary; + @apply hover:bg-badge-gradient hover:text-white; + @apply peer-hover:bg-badge-gradient peer-hover:text-white; + @apply sbw:hover:bg-none sbw:hover:bg-sbw-primary-dark; + @apply snz:hover:bg-none snz:hover:bg-snz-primary-dark; + @apply suz:hover:bg-none suz:hover:bg-suz-primary-dark; + @apply uch:hover:bg-none uch:hover:bg-uch-primary-dark; + @apply ser:hover:bg-none ser:hover:bg-ser-primary; + @apply sbw:peer-hover:bg-none sbw:peer-hover:bg-sbw-primary-dark; + @apply snz:peer-hover:bg-none snz:peer-hover:bg-snz-primary-dark; + @apply suz:peer-hover:bg-none suz:peer-hover:bg-suz-primary-dark; + @apply uch:peer-hover:bg-none uch:peer-hover:bg-uch-primary-dark; + @apply ser:peer-hover:bg-none ser:peer-hover:bg-ser-primary; +} + +.bg-button { + @apply bg-ep-primary-dark text-white; + @apply sbw:bg-sbw-secondary; + @apply snz:bg-snz-secondary; + @apply suz:bg-suz-secondary; + @apply uch:bg-uch-secondary; + @apply ser:bg-ser-secondary; + @apply hover:bg-badge-gradient hover:text-white; + @apply sbw:hover:bg-none sbw:hover:bg-sbw-primary-dark; + @apply snz:hover:bg-none snz:hover:bg-snz-primary-dark; + @apply suz:hover:bg-none suz:hover:bg-suz-primary-dark; + @apply uch:hover:bg-none uch:hover:bg-uch-primary-dark; + @apply ser:hover:bg-none ser:hover:bg-ser-primary; + + &--active, + &--secondary { + @apply bg-badge-gradient text-white; + @apply sbw:bg-none sbw:bg-sbw-secondary; + @apply snz:bg-none snz:bg-snz-secondary; + @apply suz:bg-none suz:bg-suz-secondary; + @apply uch:bg-none uch:bg-uch-secondary; + @apply ser:bg-none ser:bg-ser-secondary; + @apply hover:bg-none hover:bg-ep-pink hover:text-white; + @apply sbw:hover:bg-none sbw:hover:bg-sbw-primary-dark; + @apply snz:hover:bg-none snz:hover:bg-snz-primary-dark; + @apply suz:hover:bg-none suz:hover:bg-suz-primary-dark; + @apply uch:hover:bg-none uch:hover:bg-uch-primary-dark; + @apply ser:hover:bg-none ser:hover:bg-ser-primary; + } + + &--muted { + @apply bg-zinc-300 text-gray-900 hover:bg-zinc-300; + } + + &--dark { + @apply bg-ep-primary-dark; + @apply ser:bg-ser-primary; + @apply ser:hover:bg-ser-secondary; + } + + &--light { + @apply bg-white border-2; + @apply font-bold; + @apply border-ep-primary-dark text-ep-primary-dark; + @apply sbw:bg-white sbw:border-sbw-primary sbw:text-sbw-primary; + @apply snz:bg-white snz:border-snz-primary snz:text-snz-primary; + @apply suz:bg-white suz:border-suz-primary suz:text-suz-primary; + @apply uch:bg-white uch:border-uch-primary uch:text-uch-primary; + @apply ser:bg-white ser:border-ser-primary ser:text-ser-primary; + + &:hover, + &.button--active { + @apply bg-none bg-ep-pink border-ep-pink; + @apply sbw:bg-white sbw:border-sbw-secondary sbw:text-sbw-secondary; + @apply snz:bg-white snz:border-snz-secondary snz:text-snz-secondary; + @apply suz:bg-white suz:border-suz-secondary suz:text-suz-secondary; + @apply uch:bg-white uch:border-uch-secondary uch:text-uch-secondary; + @apply ser:bg-white ser:border-ser-secondary ser:text-ser-secondary; + } + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_calendar.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_calendar.scss new file mode 100644 index 0000000..c0aac25 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_calendar.scss @@ -0,0 +1,35 @@ +.availability-full { + @apply bg-emerald-500; +} + +.availability-partial { + @apply bg-amber-300; +} + +.availability-none { + @apply bg-red-500; +} + +.availability-full-to-none { + @apply bg-gradient-to-br from-emerald-500 to-red-500; +} + +.availability-full-to-partial { + @apply bg-gradient-to-br from-emerald-500 to-amber-300; +} + +.availability-none-to-full { + @apply bg-gradient-to-br from-red-500 to-emerald-500; +} + +.availability-partial-to-full { + @apply bg-gradient-to-br from-amber-300 to-emerald-500; +} + +.availability-none-to-partial { + @apply bg-gradient-to-br from-red-500 to-amber-300; +} + +.availability-partial-to-none { + @apply bg-gradient-to-br from-amber-300 to-red-500; +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_contact-bar.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_contact-bar.scss new file mode 100644 index 0000000..57f9e24 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_contact-bar.scss @@ -0,0 +1,37 @@ +.contact-bar { + @apply fixed z-40 bottom-0 left-0 w-full flex justify-between; + + @screen md { + @apply bottom-auto left-auto top-1/2 right-0 w-auto flex-col divide-x-0 space-y-1; + transform: translate(calc(100% - theme('spacing.8')), -50%); + } +} + +.contact-bar__item { + @apply flex-1 text-white py-2 bg-ep-primary-dark border-r border-white last:border-0; + @apply ser:bg-ser-primary sbw:bg-sbw-secondary snz:bg-snz-primary suz:bg-suz-primary uch:bg-uch-primary; + + @screen md { + @apply flex flex-none items-start transition-transform duration-300 ease-in-out p-1 shadow-md focus:text-white border-0; + &:hover { + @apply text-white; + transform: translateX(calc(-100% + theme('spacing.8'))); + } + } +} + +.contact-bar__icon { + @apply w-6 h-6 mx-auto; + + @screen md { + @apply mx-0 mr-2; + } +} + +.contact-bar__label { + @apply hidden; + + @screen md { + @apply block; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_forms.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_forms.scss new file mode 100644 index 0000000..793df8c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_forms.scss @@ -0,0 +1,22 @@ +.form-field { + @apply border-zinc-400 focus:border-zinc-800 ring-0 block w-full mt-1; + @apply focus:ring-0 focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-zinc-800; + + &--has-error { + @apply border-red-500; + } +} + +.form-checkbox, +.form-radio { + @apply border-zinc-400 focus:border-zinc-800 ring-0 focus:ring-0 text-ep-secondary sbw:text-sbw-secondary uch:text-uch-secondary h-4 w-4; + @apply focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-zinc-800; + + &--has-error { + @apply border-red-500; + } +} + +input[disabled] { + @apply cursor-not-allowed; +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_herounit.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_herounit.scss new file mode 100644 index 0000000..4f4895b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_herounit.scss @@ -0,0 +1,20 @@ +.herounit { + @apply relative w-full max-w-screen-2xl mx-auto overflow-hidden; +} + +.herounit__badge-wrapper { + @apply absolute top-0 right-0 md:right-32 w-full px-4 md:w-auto md:px-0; + @apply flex flex-row-reverse justify-between md:flex-col items-start md:items-center md:space-y-4; +} + +.herounit__badge { + @apply text-white; + + &--skipass { + @apply bg-badge-gradient ser:bg-none ser:bg-ser-secondary py-8 px-2; + } + + &--custom { + @apply mt-4 w-40 transform rotate-6; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_misc.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_misc.scss new file mode 100644 index 0000000..c0bfaa1 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_misc.scss @@ -0,0 +1,11 @@ +.headerbar { + @apply bg-ep-primary-light sbw:bg-sbw-primary text-white uppercase font-semibold py-2 px-4 mb-4; +} + +.anchor { + @apply block h-0 w-0 pt-16 -mt-16; +} + +.alert { + @apply bg-ep-primary-bg p-4 text-ep-primary-dark; +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_searchbar.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_searchbar.scss new file mode 100644 index 0000000..32f734a --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_searchbar.scss @@ -0,0 +1,11 @@ +.searchbar { + @apply hidden relative lg:block lg:container -mt-20 mb-8; + + &.sticky { + @apply fixed z-50 top-8 left-0 mt-0 mb-0 inset-x-0 shadow-lg; + } + + &.reduced { + @apply max-w-full px-0 mt-0 mb-0; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_teaser.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_teaser.scss new file mode 100644 index 0000000..8a38b93 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_teaser.scss @@ -0,0 +1,73 @@ +.product-teaser__flag { + @apply w-6 h-4 md:w-8 md:h-6; +} + +.product-teaser__label-concept { + @apply text-white uppercase font-semibold; + + [data-teasergrid-disabled-value="false"] & { + @apply text-xs md:text-base; + } +} + +.product-teaser__watchlist-icon { + @apply text-white w-6 h-6; + + [data-teasergrid-disabled-value="false"] & { + @apply w-4 h-4 md:w-6 md:h-6; + } +} + +.product-teaser__label-name { + @apply text-zinc-800 leading-none; + + [data-teasergrid-disabled-value="false"] & { + @apply text-sm md:text-base; + } +} + +.product-teaser__label-region { + @apply h-12 md:h-16 text-zinc-800 leading-none font-bold text-3xl; + + [data-teasergrid-disabled-value="false"] & { + @apply text-lg leading-none md:text-3xl md:leading-none; + } +} + +.product-teaser__label-included { + @apply pb-2 text-zinc-800 text-xl font-bold leading-none; + + [data-teasergrid-disabled-value="false"] & { + @apply text-sm md:text-lg lg:text-xl; + } +} + +.product-teaser__list-included { + [data-teasergrid-disabled-value="false"] & { + @apply text-sm md:text-base; + } +} + +.product-teaser__list-icon { + @apply w-6 h-6 text-ep-primary; + + [data-teasergrid-disabled-value="false"] & { + @apply w-4 h-4 md:w-6 md:h-6; + } +} + +.product-teaser__price { + @apply text-3xl; + + [data-teasergrid-disabled-value="false"] & { + @apply text-xl md:text-3xl; + } +} + +.product-teaser__label-price { + @apply inline-block; + + [data-teasergrid-disabled-value="false"] & { + @apply text-sm md:text-base lg:inline-block; + } +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_topnav.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_topnav.scss new file mode 100644 index 0000000..efd5fe3 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_topnav.scss @@ -0,0 +1,20 @@ +.topnav { + @apply hidden lg:block w-full fixed top-0 inset-x-0 z-50 bg-ep-primary-medium ser:bg-ser-primary shadow; +} + +.topnav__nav { + @apply flex items-center m-0 p-0; +} + +.topnav__item { + @apply flex items-center justify-center w-16 h-8 text-white hover:text-white focus:text-white hover:bg-ep-secondary; + @apply ser:hover:bg-ser-secondary ser:hover:text-white; + @apply sbw:hover:bg-sbw-secondary sbw:hover:text-white; + @apply border-r border-white/50; +} + +.topnav__item--active { + @apply bg-ep-secondary text-gray-900; + @apply sbw:bg-sbw-secondary sbw:text-white; + @apply ser:bg-ser-secondary ser:text-white; +} \ No newline at end of file diff --git a/packages/ep_theme/Resources/Private/Assets/scss/components/_travelinfo.scss b/packages/ep_theme/Resources/Private/Assets/scss/components/_travelinfo.scss new file mode 100644 index 0000000..b0bfdff --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/components/_travelinfo.scss @@ -0,0 +1,19 @@ +.travel-info-box { + @apply flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white px-8 lg:px-0 py-8; +} + +.travel-info-box-title { + @apply lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8; +} + +.travel-info-box-title__inner { + @apply text-2xl lg:text-4xl text-white uppercase font-bold; +} + +.travel-info-box-content { + @apply lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white; +} + +.travel-info-box a { + color: inherit !important; +} diff --git a/packages/ep_theme/Resources/Private/Assets/scss/main.scss b/packages/ep_theme/Resources/Private/Assets/scss/main.scss new file mode 100644 index 0000000..5dc7023 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/main.scss @@ -0,0 +1,18 @@ +@use "sass:meta"; + +// Outside the cascade layers on purpose: @font-face is invalid inside @layer, which is why +// _base.scss leaves base/fonts out. Loading it here lets Webpack resolve the url()s to +// content-hashed filenames, and replaces the hand-maintained @font-face block that used to +// live in ep_theme Page/Main.typoscript. +@include meta.load-css("base/fonts"); + +@include meta.load-css("tailwindcss/base"); +@include meta.load-css("base"); + +@include meta.load-css("tailwindcss/components"); +@include meta.load-css("components"); + +@include meta.load-css("tailwindcss/utilities"); +@include meta.load-css("utilities"); + +@include meta.load-css("vendor"); diff --git a/packages/ep_theme/Resources/Private/Assets/scss/rte.scss b/packages/ep_theme/Resources/Private/Assets/scss/rte.scss new file mode 100644 index 0000000..5fd3059 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Assets/scss/rte.scss @@ -0,0 +1,30 @@ +@use "base/fonts"; + +body { + @apply antialiased text-zinc-800 font-sans; +} + +table { + border: 1px solid #ccc; + border-collapse: collapse; + font-size: inherit; +} + +a.rte-button { + @apply inline-flex items-center justify-center px-4 py-2 md:px-8 cursor-pointer; + @apply uppercase leading-none text-center no-underline; + @apply bg-ep-primary-dark text-white; +} + +a.rte-button--full { + @extend .rte-button; + @apply w-full; +} + +td, th { + border: 1px solid #ccc; + border-collapse: collapse; + vertical-align: top; + text-align: left; + padding: 3px; +} diff --git a/packages/ep_theme/Resources/Private/Forms/AdresscheckForm.form.yaml b/packages/ep_theme/Resources/Private/Forms/AdresscheckForm.form.yaml new file mode 100644 index 0000000..f7241ed --- /dev/null +++ b/packages/ep_theme/Resources/Private/Forms/AdresscheckForm.form.yaml @@ -0,0 +1,144 @@ + +renderingOptions: + submitButtonLabel: Absenden +type: Form +identifier: adresscheck-ext +label: Adresscheck (EXT) +prototypeName: standard +finishers: +- + options: + subject: Adresscheck + recipientAddress: 'info@ep-reisen.de' + recipientName: 'E&P Reisen' + senderAddress: smtprelay@ep-reisen.de + senderName: 'E&P Reisen' + replyToAddress: '' + carbonCopyAddress: '' + blindCarbonCopyAddress: '' + format: html + attachUploads: false + translation: + language: '' + identifier: EmailToReceiver +- + identifier: SaveToDatabase + options: + - + table: 'tx_eptheme_domain_model_addresscheck' + mode: insert + databaseColumnMappings: + pid: + value: 1228 + elements: + customer-id: + mapOnDatabaseColumn: 'customer_id' + title: + mapOnDatabaseColumn: 'title' + first-name: + mapOnDatabaseColumn: 'first_name' + last-name: + mapOnDatabaseColumn: 'last_name' + address: + mapOnDatabaseColumn: 'address' + zip: + mapOnDatabaseColumn: 'zip' + city: + mapOnDatabaseColumn: 'city' + country: + mapOnDatabaseColumn: 'country' + email: + mapOnDatabaseColumn: 'email' + no-postal: + mapOnDatabaseColumn: 'no_postal' + no-email: + mapOnDatabaseColumn: 'no_email' + address-is-current-postal: + mapOnDatabaseColumn: 'address_current' +- + options: + pageUid: '1606' + additionalParameters: '' + identifier: Redirect +renderables: +- + renderingOptions: + previousButtonLabel: '' + nextButtonLabel: '' + type: Page + identifier: page-1 + label: '' + renderables: + - + defaultValue: '' + type: Hidden + identifier: customer-id + label: KundenID + - + defaultValue: '' + type: Text + identifier: title + label: Anrede + - + defaultValue: '' + type: Text + identifier: first-name + label: Vorname + properties: + fluidAdditionalAttributes: + required: required + - + defaultValue: '' + type: Text + identifier: last-name + label: Name + properties: + fluidAdditionalAttributes: + required: required + - + defaultValue: '' + type: Text + identifier: address + label: 'Straße & Hausnummer' + properties: + elementDescription: '' + - + defaultValue: '' + type: Text + identifier: zip + label: PLZ + - + defaultValue: '' + type: Text + identifier: city + label: Stadt + - + defaultValue: '' + type: Text + identifier: country + label: Land + - + defaultValue: '' + type: Text + identifier: email + label: E-Mail + properties: + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty + - + identifier: EmailAddress + - + type: Checkbox + identifier: no-postal + label: 'Postalische Werbung abbestellen' + - + type: Checkbox + identifier: no-email + label: 'Ich möchte keine Mails mehr erhalten.' + - + type: Checkbox + identifier: address-is-current + label: 'Meine Adresse ist aktuell.' diff --git a/packages/ep_theme/Resources/Private/Forms/AdresscheckRemove.form.yaml b/packages/ep_theme/Resources/Private/Forms/AdresscheckRemove.form.yaml new file mode 100644 index 0000000..f3d8627 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Forms/AdresscheckRemove.form.yaml @@ -0,0 +1,118 @@ + +renderingOptions: + submitButtonLabel: aus Mailingliste entfernen +type: Form +identifier: adresscheck-remove-ext +label: Adresscheck aus Datenbank entfernen (EXT) +prototypeName: standard +finishers: +- + options: + subject: 'Adresscheck - aus Datenbank entfernen' + recipientAddress: 'info@ep-reisen.de' + recipientName: 'E&P Reisen' + senderAddress: smtprelay@ep-reisen.de + senderName: 'E&P Reisen' + replyToAddress: '' + carbonCopyAddress: '' + blindCarbonCopyAddress: '' + format: html + attachUploads: false + translation: + language: '' + identifier: EmailToReceiver +- + identifier: SaveToDatabase + options: + - + table: 'tx_eptheme_domain_model_addresscheck' + mode: insert + databaseColumnMappings: + pid: + value: 1609 + elements: + customer-id: + mapOnDatabaseColumn: 'customer_id' + title: + mapOnDatabaseColumn: 'title' + first-name: + mapOnDatabaseColumn: 'first_name' + last-name: + mapOnDatabaseColumn: 'last_name' + address: + mapOnDatabaseColumn: 'address' + zip: + mapOnDatabaseColumn: 'zip' + city: + mapOnDatabaseColumn: 'city' + country: + mapOnDatabaseColumn: 'country' + email: + mapOnDatabaseColumn: 'email' + no-postal: + mapOnDatabaseColumn: 'no_postal' + no-email: + mapOnDatabaseColumn: 'no_email' + address-is-current-postal: + mapOnDatabaseColumn: 'address_current' +- + options: + pageUid: '1610' + additionalParameters: '' + identifier: Redirect +renderables: +- + renderingOptions: + previousButtonLabel: '' + nextButtonLabel: '' + type: Page + identifier: page-1 + label: '' + renderables: + - + defaultValue: '' + type: Hidden + identifier: customer-id + label: KundenID + - + defaultValue: '' + type: Hidden + identifier: title + label: Anrede + - + defaultValue: '' + type: Hidden + identifier: first-name + label: Vorname + - + defaultValue: '' + type: Hidden + identifier: last-name + label: Name + - + defaultValue: '' + type: Hidden + identifier: address + label: 'Straße & Hausnummer' + properties: + elementDescription: '' + - + defaultValue: '' + type: Hidden + identifier: zip + label: PLZ + - + defaultValue: '' + type: Hidden + identifier: city + label: Stadt + - + defaultValue: '' + type: Hidden + identifier: country + label: Land + - + defaultValue: '' + type: Hidden + identifier: email + label: E-Mail diff --git a/packages/ep_theme/Resources/Private/Forms/ApplicationForm.form.yaml b/packages/ep_theme/Resources/Private/Forms/ApplicationForm.form.yaml new file mode 100644 index 0000000..be73b78 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Forms/ApplicationForm.form.yaml @@ -0,0 +1,378 @@ + +renderingOptions: + submitButtonLabel: Absenden +type: Form +identifier: applicationform +label: Bewerbungsformular +prototypeName: applicationform +finishers: + - + options: + subject: 'Bewerbung von {text-1}' + recipientAddress: team@ep-reisen.de + recipientName: 'E&P Reisen' + senderAddress: team@ep-reisen.de + senderName: '{text-1}' + replyToAddress: '{text-6}' + carbonCopyAddress: '' + blindCarbonCopyAddress: '' + format: html + attachUploads: false + translation: + language: '' + identifier: EmailToReceiver + - + options: + message: 'Vielen Dank für deine Bewerbung. Wir werden sie so schnell wie möglich bearbeiten.' + identifier: Confirmation +renderables: + - + renderingOptions: + previousButtonLabel: zurück + nextButtonLabel: weiter + type: Page + identifier: page-1 + label: '' + renderables: + - + defaultValue: '' + type: Text + identifier: text-1 + label: Name + properties: + fluidAdditionalAttributes: + placeholder: 'Vor- & Nachname' + required: required + elementDescription: '' + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: text-2 + label: Straße + properties: + fluidAdditionalAttributes: + placeholder: 'Straße & Hausnummer' + required: required + elementDescription: '' + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: text-3 + label: Wohnort + properties: + fluidAdditionalAttributes: + placeholder: 'PLZ & Ort' + required: required + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: text-4 + label: 'Mobil Nummer' + properties: + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: text-5 + label: 'Festnetz Nummer' + - + defaultValue: '' + type: Text + identifier: text-6 + label: 'E-Mail Adresse' + properties: + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty + - + identifier: EmailAddress + - + defaultValue: '' + type: Text + identifier: text-7 + label: Geburtsdatum + properties: + fluidAdditionalAttributes: + placeholder: tt.mm.jjjj + required: required + validators: + - + options: + regularExpression: '/\d{2}\.\d{2}\.\d{4}/' + identifier: RegularExpression + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: text-8 + label: Nationalität + - + properties: + options: + nein: nein + ja: ja + 'ja, mit Anhänger': 'ja, mit Anhänger' + type: RadioButton + identifier: radiobutton-1 + label: Führerschein + - + defaultValue: '' + type: Text + identifier: text-9 + label: Ausbildung/Studium + properties: + fluidAdditionalAttributes: + placeholder: 'Schul-, Uni-, Berufsausbildung' + elementDescription: '' + - + defaultValue: '' + type: Text + identifier: text-10 + label: 'Aktuelle Tätigkeit' + - + properties: + options: + Haus-/Destinationsleitung: Haus-/Destinationsleitung + Koch/Köchin: Koch/Köchin + Koch-Assistenz/Service: Koch-Assistenz/Service + 'Serviceleitung/Assistenz Hausleitung': 'Serviceleitung/Assistenz Hausleitung' + Serviceteamer: Serviceteamer + Reinigungskraft: Reinigungskraft + Materialteamer/Verleih: Materialteamer/Verleih + Allrounder: Allrounder + 'Praktikum im Schnee': 'Praktikum im Schnee' + elementDescription: '' + type: MultiCheckbox + identifier: multicheckbox-1 + label: 'Bewerbung als' + - + defaultValue: '' + type: Text + identifier: text-11 + label: 'Einsatz datum' + properties: + fluidAdditionalAttributes: + placeholder: 'von tt.mm.jjjj bis tt.mm.jjjj' + required: required + validators: + - + identifier: NotEmpty + - + properties: + options: + Ski: Ski + Snowboard: Snowboard + nichts: nichts + type: MultiCheckbox + identifier: multicheckbox-2 + label: 'Ich fahre' + validators: + - + identifier: NotEmpty + - + options: + minimum: '1' + maximum: '2' + identifier: Count + - + properties: + options: + 'noch nicht gelernt': 'noch nicht gelernt' + Anfänger: Anfänger + Fortgeschritten: Fortgeschritten + Experte: Experte + type: SingleSelect + identifier: singleselect-1 + label: 'Wie gut fahre ich Ski' + defaultValue: 'noch nicht gelernt' + - + defaultValue: '' + type: Text + identifier: text-12 + label: 'Skifahrer seit' + - + properties: + options: + 'noch nicht gelernt': 'noch nicht gelernt' + Anfänger: Anfänger + Fortgeschritten: Fortgeschritten + Experte: Experte + type: SingleSelect + identifier: singleselect-2 + label: 'Wie gut fahre ich Snowboard' + - + defaultValue: '' + type: Text + identifier: text-13 + label: 'Snowboarder seit' + properties: + elementDescription: '' + - + properties: + options: + nein: nein + 'Ski Grundstufe': 'Ski Grundstufe' + 'Board Grundstufe': 'Board Grundstufe' + 'Ski Instructor': 'Ski Instructor' + 'Board Instructor': 'Board Instructor' + 'Ski Begleitschein': 'Ski Begleitschein' + 'Board Begleitschein': 'Board Begleitschein' + type: MultiSelect + identifier: multiselect-1 + label: Ski-/Snowboardlehrerschein + defaultValue: + - nein + - + type: Fieldset + identifier: fieldset-1 + label: Fremdsprachen + renderables: + - + properties: + options: + nein: nein + Grundkenntnisse: Grundkenntnisse + gut: gut + 'sehr gut': 'sehr gut' + type: SingleSelect + identifier: singleselect-3 + label: Englisch + defaultValue: nein + - + properties: + options: + nein: nein + Grundkenntnisse: Grundkenntnisse + gut: gut + 'sehr gut': 'sehr gut' + type: SingleSelect + identifier: singleselect-4 + label: Französisch + defaultValue: nein + - + properties: + options: + nein: nein + Grundkenntnisse: Grundkenntnisse + gut: gut + 'sehr gut': 'sehr gut' + type: SingleSelect + identifier: singleselect-5 + label: Spanisch + defaultValue: nein + - + defaultValue: '' + type: Text + identifier: text-14 + label: 'Sonstige Fremdsprachen' + - + properties: + options: + ja: ja + nein: nein + type: RadioButton + identifier: radiobutton-2 + label: 'Kocherfahrung für Gruppen (min. 30 Personen)' + - + defaultValue: '' + type: Text + identifier: text-15 + label: 'Wenn ja, welche?' + properties: + elementDescription: '' + - + properties: + options: + ja: ja + nein: nein + type: RadioButton + identifier: radiobutton-3 + label: 'Erfahrungen im Tourismus' + - + defaultValue: '' + type: Text + identifier: text-16 + label: 'Wenn ja, welche?' + properties: + elementDescription: '' + - + properties: + options: + 'ja, mit Zertifikat zur Bindungseinstellung': 'ja, mit Zertifikat zur Bindungseinstellung' + nein: nein + type: RadioButton + identifier: radiobutton-4 + label: 'Erfahrungen im Skiverleih' + - + properties: + options: + Zillertal: Zillertal + Lenzerheide: Lenzerheide + 'Davos Klosters': 'Davos Klosters' + Montafon: Montafon + Saalbach: Saalbach + 'Les Deux Alpes': 'Les Deux Alpes' + '4 Vallées': '4 Vallées' + 'Val Thorens': 'Val Thorens' + 'Les Sybelles': 'Les Sybelles' + 'Portes du Soleil': 'Portes du Soleil' + type: MultiCheckbox + identifier: multicheckbox-3 + label: 'Ortskenntnisse in folgenden Destinationen' + - + properties: + options: + 'Damen 36': 'Damen 36' + 'Damen 38': 'Damen 38' + 'Damen 40': 'Damen 40' + 'Damen 42': 'Damen 42' + 'Herren S': 'Herren S' + 'Herren M': 'Herren M' + 'Herren L': 'Herren L' + 'Herren XL': 'Herren XL' + type: SingleSelect + identifier: singleselect-6 + label: Jackengröße + - + defaultValue: '' + type: Textarea + identifier: textarea-1 + label: 'Warum möchte ich für E&P Reisen arbeiten?' + - + properties: + options: + Freunde: Freunde + 'Social Media': 'Social Media' + Uni: Uni + Sonstiges: Sonstiges + type: MultiCheckbox + identifier: multicheckbox-4 + label: 'Wie bin ich auf E&P Reisen aufmerksam geworden?' + - + type: LinkedCheckbox + identifier: privacypolicyaccepted + label: 'Ich akzeptiere die %s.' + properties: + pageUid: '355' + linkText: 'Datenschutzbestimmungen' + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty diff --git a/packages/ep_theme/Resources/Private/Forms/ContactForm.form.yaml b/packages/ep_theme/Resources/Private/Forms/ContactForm.form.yaml new file mode 100644 index 0000000..3a0ec95 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Forms/ContactForm.form.yaml @@ -0,0 +1,148 @@ + +renderingOptions: + submitButtonLabel: Absenden +type: Form +identifier: contactform +label: Kontaktformular +prototypeName: contactform +finishers: + - + identifier: BpnApiFinisher + options: + mapping: 'lastName:lastName,firstName:firstName,gender:gender,email:email,street:street,zipCode:zipCode,city:city,phone:phone' + - + options: + subject: 'Kontaktanfrage von {firstName} {lastName}' + recipientAddress: info@ep-reisen.de + recipientName: 'E&P Reisen' + senderAddress: info@ep-reisen.de + senderName: '{firstName} {lastName}' + replyToAddress: '{email}' + carbonCopyAddress: '' + blindCarbonCopyAddress: '' + format: html + attachUploads: false + translation: + language: '' + identifier: EmailToReceiver + - + options: + pageUid: '1387' + additionalParameters: '' + identifier: Redirect +renderables: + - + renderingOptions: + previousButtonLabel: 'Previous step' + nextButtonLabel: 'Neue Seite' + type: Page + identifier: page-1 + label: '' + renderables: + - + defaultValue: '' + type: Text + identifier: firstName + label: Vorname + properties: + fluidAdditionalAttributes: + placeholder: 'Vorname' + required: required + elementDescription: '' + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: lastName + label: 'Nachname' + properties: + fluidAdditionalAttributes: + placeholder: 'Nachname' + required: required + elementDescription: '' + validators: + - + identifier: NotEmpty + - + properties: + options: + M: M + W: W + D: D + fluidAdditionalAttributes: + required: required + type: SingleSelect + identifier: gender + label: 'Gender' + validators: + - + identifier: NotEmpty + - + defaultValue: '' + type: Text + identifier: email + label: 'E-Mail' + properties: + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty + - + identifier: EmailAddress + - + defaultValue: '' + type: Text + identifier: street + label: 'Straße' + properties: + fluidAdditionalAttributes: + placeholder: 'Straße & Nr.' + elementDescription: '' + - + defaultValue: '' + type: Text + identifier: zipCode + label: 'PLZ' + - + defaultValue: '' + type: Text + identifier: city + label: 'Ort' + - + defaultValue: '' + type: Text + identifier: phone + label: 'Telefon' + - + defaultValue: '' + type: Textarea + identifier: message + label: 'Deine Kontaktanfrage' + - + defaultValue: '' + type: Text + identifier: bookingNumber + label: 'Buchungsnummer (falls vorhanden)' + - + type: LinkedCheckbox + identifier: privacypolicyaccepted + label: 'Ich akzeptiere die %s.' + properties: + pageUid: '355' + linkText: 'Datenschutzbestimmungen' + fluidAdditionalAttributes: + required: required + validators: + - + identifier: NotEmpty + - + type: Captcha + identifier: captcha + label: Captcha + properties: + fluidAdditionalAttributes: + required: required + autocomplete: 'off' diff --git a/packages/ep_theme/Resources/Private/Language/de.locallang.xlf b/packages/ep_theme/Resources/Private/Language/de.locallang.xlf new file mode 100644 index 0000000..5ecbc1e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Language/de.locallang.xlf @@ -0,0 +1,113 @@ + + + +
+ + + Slider + + + Bezeichnung + + + Slides + + + Slide + + + Headline + + + Subline + + + Bilder + + + Button + + + Link + + + + Details + + + Zum Post + + + Details & Buchen + + + Details + + + Direkt zum Anfrageformular + + + Lineup + + + Name + + + Beschreibung + + + Bild + + + Facebook URL + + + Twitter URL + + + Instagram URL + + + Youtube URL + + + + Datum von + + + Datum bis + + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte gib eine gültige E-Mail Adresse an + + + + Bitte wähle mindestens einen Newsletter + + + Bitte stimme den Datenschutzbestimmungen zu + + + + Nummer einfach als neuen Kontakt abspeichern und los geht es! Wir beraten Dich über WhatsApp! + + + + diff --git a/packages/ep_theme/Resources/Private/Language/locallang.xlf b/packages/ep_theme/Resources/Private/Language/locallang.xlf new file mode 100644 index 0000000..8c0d32c --- /dev/null +++ b/packages/ep_theme/Resources/Private/Language/locallang.xlf @@ -0,0 +1,132 @@ + + + +
+ + + Slider + + + Bezeichnung + + + Slides + + + Slide + + + Headline + + + Subline + + + Bilder + + + Button + + + Link + + + + Details + + + Zum Post + + + Details & Buchen + + + Details + + + Directly to the request form + + + Lineup + + + Name + + + Beschreibung + + + Bild + + + Facebook URL + + + Twitter URL + + + Instagram URL + + + Youtube URL + + + + Datum von + + + Datum bis + + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte auswählen + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Bitte angeben + + + Ungültige E-Mail Adresse + + + Ungültige E-Mail Adresse + + + Deine Bestätigung ist erforderlich + + + + Nummer einfach als neuen Kontakt abspeichern und los geht es! Wir beraten Dich über WhatsApp! + + + + diff --git a/packages/ep_theme/Resources/Private/Language/locallang_db.xlf b/packages/ep_theme/Resources/Private/Language/locallang_db.xlf new file mode 100644 index 0000000..a730cd2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Language/locallang_db.xlf @@ -0,0 +1,74 @@ + + + +
+ + + Slider + + + Bezeichnung + + + Slides + + + Slide + + + Headline + + + Subline + + + Bilder + + + Button + + + Link + + + Lineup + + + Name + + + Beschreibung + + + Bild + + + Facebook URL + + + Twitter URL + + + Instagram URL + + + Youtub URL + + + Siegel + + + Titel + + + Untertitel + + + Link + + + Seite + + + + diff --git a/packages/ep_theme/Resources/Private/Language/locallang_mod.xlf b/packages/ep_theme/Resources/Private/Language/locallang_mod.xlf new file mode 100644 index 0000000..ec927ef --- /dev/null +++ b/packages/ep_theme/Resources/Private/Language/locallang_mod.xlf @@ -0,0 +1,17 @@ + + + +
+ + + E&P SEO Übersicht + + + Page title und Meta Description Übersicht + + + Seiten + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Email/SystemEmail.html b/packages/ep_theme/Resources/Private/Layouts/Email/SystemEmail.html new file mode 100644 index 0000000..bd3f29f --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Email/SystemEmail.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+ + +
+
+ + + + + + + + + + + +
+ +
+ +
+ + +
+
+ + +
+ + +
+
+ + + + + + + + +
+ + + +
+ + +
+
+ + +
+
+
+ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Bare.html b/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Bare.html new file mode 100644 index 0000000..d2413b0 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Bare.html @@ -0,0 +1 @@ + diff --git a/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Default.html b/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Default.html new file mode 100644 index 0000000..4a12bcd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/FluidStyledContent/Default.html @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Form/Default.html b/packages/ep_theme/Resources/Private/Layouts/Form/Default.html new file mode 100644 index 0000000..8027861 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Form/Default.html @@ -0,0 +1 @@ + diff --git a/packages/ep_theme/Resources/Private/Layouts/Gridelements/Default.html b/packages/ep_theme/Resources/Private/Layouts/Gridelements/Default.html new file mode 100644 index 0000000..5852942 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Gridelements/Default.html @@ -0,0 +1,2 @@ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/News/Detail.html b/packages/ep_theme/Resources/Private/Layouts/News/Detail.html new file mode 100644 index 0000000..dbdac17 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/News/Detail.html @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Backend.html b/packages/ep_theme/Resources/Private/Layouts/Page/Backend.html new file mode 100644 index 0000000..a606ca0 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Backend.html @@ -0,0 +1 @@ + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Detail.html b/packages/ep_theme/Resources/Private/Layouts/Page/Detail.html new file mode 100644 index 0000000..6818ea7 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Detail.html @@ -0,0 +1,23 @@ + + + + + + + +
+ + +
+
+ +
+ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Frontpage.html b/packages/ep_theme/Resources/Private/Layouts/Page/Frontpage.html new file mode 100644 index 0000000..093cef2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Frontpage.html @@ -0,0 +1,31 @@ + + + + +
+ +
+
+ + + + + + +
+
+ +
+ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/FrontpageEvent.html b/packages/ep_theme/Resources/Private/Layouts/Page/FrontpageEvent.html new file mode 100644 index 0000000..b95d455 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/FrontpageEvent.html @@ -0,0 +1,22 @@ + + + +
+ + +
+
+ +
+
+ +
+ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Popup.html b/packages/ep_theme/Resources/Private/Layouts/Page/Popup.html new file mode 100644 index 0000000..c0d8ba9 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Popup.html @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Search.html b/packages/ep_theme/Resources/Private/Layouts/Page/Search.html new file mode 100644 index 0000000..2a564bd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Search.html @@ -0,0 +1,18 @@ + + + + +
+ +
+
+ +
+ + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Subpage.html b/packages/ep_theme/Resources/Private/Layouts/Page/Subpage.html new file mode 100644 index 0000000..215d855 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Subpage.html @@ -0,0 +1,31 @@ + + + +
+ + + + + + +
+
+ + + +
+
+ +
+ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeft.html b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeft.html new file mode 100644 index 0000000..8e2ca53 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeft.html @@ -0,0 +1,35 @@ + + + +
+ + + + + + +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeftEvent.html b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeftEvent.html new file mode 100644 index 0000000..1574c07 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarLeftEvent.html @@ -0,0 +1,29 @@ + + + +
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRight.html b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRight.html new file mode 100644 index 0000000..9336a40 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRight.html @@ -0,0 +1,35 @@ + + + +
+ + + + + + +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRightEvent.html b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRightEvent.html new file mode 100644 index 0000000..f3492f2 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/SubpageSidebarRightEvent.html @@ -0,0 +1,29 @@ + + + +
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + + diff --git a/packages/ep_theme/Resources/Private/Layouts/Page/Travelinfo.html b/packages/ep_theme/Resources/Private/Layouts/Page/Travelinfo.html new file mode 100644 index 0000000..b89f0a4 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Page/Travelinfo.html @@ -0,0 +1,28 @@ + + +
+ + + + +
Login
+
My E&P
+
+
+
+ +
+
+ +
+ + diff --git a/packages/ep_theme/Resources/Private/Layouts/Product/Default.html b/packages/ep_theme/Resources/Private/Layouts/Product/Default.html new file mode 100644 index 0000000..8027861 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Layouts/Product/Default.html @@ -0,0 +1 @@ + diff --git a/packages/ep_theme/Resources/Private/Partials/Badge.html b/packages/ep_theme/Resources/Private/Partials/Badge.html new file mode 100644 index 0000000..73bf15e --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Badge.html @@ -0,0 +1,29 @@ + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Partials/Chatbot.html b/packages/ep_theme/Resources/Private/Partials/Chatbot.html new file mode 100644 index 0000000..c8f5d62 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/Chatbot.html @@ -0,0 +1,58 @@ + + +
+ + diff --git a/packages/ep_theme/Resources/Private/Partials/City/Details.html b/packages/ep_theme/Resources/Private/Partials/City/Details.html new file mode 100644 index 0000000..d6a79a6 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Partials/City/Details.html @@ -0,0 +1,154 @@ +{namespace ep=EP\EpTheme\ViewHelpers} +
+
+ + +
+ {city.name} +
+
+ +

+ Ort +

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

+ Party +

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

+ Freizeitangebot +

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

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

+

+ +

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

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

+

+ +

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

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

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

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

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

+
+ +

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

+
+ +

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

+
+ +

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

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

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

+
+ +

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

+
+ +

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

+
+ +

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

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

Ski-
Pass

+

inklusive

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

+ {hotelTitle} +

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

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

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

+ Ausstattung +

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

Apartments

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

+ Ferienwohnungen +

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

+ Zimmerarten +

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

+ Zusatzinformationen +

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

+ Busanreise +

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

+ Eigenanreise +

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

    + {newsItem.title} +

    + +
    +

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

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

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

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

    + Officetipp +

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

    + Konzept +

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

    + Verpflegung +

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

    + Inklusivleistungen +

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

    + Zusatzleistungen +

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

    + Programm +

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

    + {product.skiPassAltTitle} +

    +
    + +

    + Skipass, Kurse, Leihmaterial +

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

    + Ski-
    Pass +

    +

    + inklusive +

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

    + Bus-Zustiege +

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

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

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

    + {label} +

    + +
    +
    + +
    +

    + Kundenstimmen +

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

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

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

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

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

    + Aktuelles {region.name} +

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

    + Webcam +

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

    + Pistenplan +

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

    + Gebietsinfo +

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

    + Suchergebnis +

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

    + {section.concept.name} +

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

    + Schneehöhe aktuell +

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

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

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

    Ski-
    Pass

    +

    inklusive

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

    + Teamer vor Ort +

    + + + +

    {teamerInCharge.name}

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

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

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

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

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

    + Adresse +

    +

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

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

    + Eigenanreise +

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

    + Sei vorbereitet! + Organisatorisches +

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

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

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

    + {teaser.title} +

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

    + Folgende Kurse finden statt +

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

    + Bei Eurer Reise finden keine Kurse statt. +

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

    + Bus-Zustiege +

    +

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

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

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

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

    + Bus-Zustiege +

    +

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

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

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

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

    + Preise {year} +

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

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

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

    * Preis kommt durch eine Unterbelegung zustande.

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

    + {concept.name} +

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

    + {section.concept.name} +

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

    SEO Übersicht

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

    Sehr geehrte Damen und Herren,

    +

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

    +

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

    +

    Vielen Dank.

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

    Anfrage E&P Reisen vom

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

    Buchung Gruppenhaus vom

    +
    + +

    Anfrage Gruppenhaus vom

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

    Belegungskalender {hotel.0.data.name}

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

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

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

    Lage

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

    Buchungsoptionen

    +
    + +
    +
    +

    Ausstattung

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

    Zimmerarten / Apartments

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

    Zusatzinformationen

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

    + {newsItem.title} +

    +

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

    + +

    + {newsItem.teaser} +

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

    + Dateien +

    + +
    + +

    + Ähnliche Beiträge +

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

    + Blog: {tags.0.title} +

    +
    + +

    + Blog +

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

    Themen

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

    + News & Angebote per Mail +

    +
    + + + + + +

    + Benachrichtige mich bei Verfügbarkeit +

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

    + Vielen Dank für dein Interesse +

    +

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

    +

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

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

    + Störer +

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

    + Störer +

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

    + OSM Karte +

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

    + Lineup +

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

    + Teaser Buchungslink +

    +

    + + + + + + + + +

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

    + Teaser Ort +

    +

    + + + + + + + + +

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

    + Teaser Konzept +

    +

    + + + + + + + + +

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

    + Teaser Land +

    +

    + + + + + + + + +

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

    + Teaser Hotel +

    +

    + + + + + + + + +

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

    + Teaser Produkt +

    +

    + + + + + + + + +

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

    + Teaser Gebiet +

    +

    + + + + + + + + +

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

    + Gruppenrabatt bis zu 100€ möglich +

    +

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

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

    + 360°-Rundgang +

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

    + Lage +

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

    + Galerie +

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

    Datenexport {productData.name}

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

    + +

    +
    + +
    +
    + + +
    +
    +

    + +

    +
    + +
    +
    + + +
    +
    +

    + +

    +
    + +
    +
    + + +
    +
    +

    + +

    +
    + +
    +
    + + +
    +
    +

    + +

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

    {reseller.name}

    +

    Gesamtexport XML

    + + Export XML + +

    Gesamtexport CSV (Facebook)

    + + Export CSV (Google) + +

    Gesamtexport CSV (Google)

    + + Export CSV (Facebook) + +

    Einzelexport für Copy&Paste

    +
      + +
    • {product.nameInternal}
    • +
      +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Search/Searchresult.html b/packages/ep_theme/Resources/Private/Templates/Search/Searchresult.html new file mode 100644 index 0000000..c5c760d --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Search/Searchresult.html @@ -0,0 +1,29 @@ + + + + + +
    +
    +
    + +
    + +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Searchbar/Index.html b/packages/ep_theme/Resources/Private/Templates/Searchbar/Index.html new file mode 100644 index 0000000..de17575 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Searchbar/Index.html @@ -0,0 +1,279 @@ + + + + + +
    + data-searchbar-collapsed-value="true" + + data-action="datepicker:range-select->searchbar#setDateRange searchbar-toggle:toggle@window->searchbar#toggle"> + + + + + + + +
    + + data-controller="sticky" + data-sticky-offset-value="650" + data-sticky-padding-value="7.5rem" + data-sticky-disabled-value="false" + data-sticky-classes-value="sticky reduced" + + + data-searchbar-target="container"> +
    +
    +
    + +
    +
    + + + + + +
    +
    + +
    +
    + + + + + +
    +
    + +
    +
    + + + + + +
    +
    + +
    +
    + + + + + +
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Snowreport/List.html b/packages/ep_theme/Resources/Private/Templates/Snowreport/List.html new file mode 100644 index 0000000..1da3b32 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Snowreport/List.html @@ -0,0 +1,53 @@ + + + + + + +
    + +
    +
    + {ep:icon(icon: 'flag-{snowreport.countryCode -> f:format.case(mode: \'lower\')}', class: 'w-8 h-6')} + + {snowreport.regionName} + +
    +
    + + +
      +
    • + Berg/Tal: {snowreport.snow_level_mountain -> v:or(alternative: '-')}/{snowreport.snow_level_valley -> v:or(alternative: '-')} cm +
    • +
    • + Qualität: {snowreport.snow_quality} +
    • +
    • + Geöffnete Lifte: {snowreport.lifts_opened}/{snowreport.lifts_total}keine Angabe +
    • +
    • + Letzter Schneefall: {f:if(condition: snowreport.last_snowfall, then: '{snowreport.last_snowfall -> f:format.date(format: \'%d.%m.%Y\')}', else: 'keine Angabe')} +
    • +
    • + Letzte Akt.: {snowreport.last_update -> f:format.date(format: '%d.%m.%Y')} +
    • +
    +
    + + Saison vom {snowreport.seasonStart -> f:format.date(format: '%d.%m.%Y')} bis {snowreport.seasonEnd -> f:format.date(format: '%d.%m.%Y')} + +
    +
    +
    +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Team/List.html b/packages/ep_theme/Resources/Private/Templates/Team/List.html new file mode 100644 index 0000000..d3edd32 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Team/List.html @@ -0,0 +1,49 @@ + + + + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Team/Show.html b/packages/ep_theme/Resources/Private/Templates/Team/Show.html new file mode 100644 index 0000000..645862b --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Team/Show.html @@ -0,0 +1,48 @@ + + + + + +
    +
    +
    + + + +
    +
    + +
    + {member.description -> f:format.html()} +
    + +
    + + + + + + {member.email} + + + + + + + + {member.phone} + + +
    +
    + + zurück + +
    +
    +
    +
    + + diff --git a/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelCode.html b/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelCode.html new file mode 100644 index 0000000..9015bcd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelCode.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelInfo.html b/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelInfo.html new file mode 100644 index 0000000..9015bcd --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Travelinfo/TravelInfo.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/packages/ep_theme/Resources/Private/Templates/Watchlist/List.html b/packages/ep_theme/Resources/Private/Templates/Watchlist/List.html new file mode 100644 index 0000000..ccb69c5 --- /dev/null +++ b/packages/ep_theme/Resources/Private/Templates/Watchlist/List.html @@ -0,0 +1,23 @@ + + + + + + +
    +
    + +
    +
    +
    + + diff --git a/packages/ep_theme/composer.json b/packages/ep_theme/composer.json new file mode 100644 index 0000000..cacc62f --- /dev/null +++ b/packages/ep_theme/composer.json @@ -0,0 +1,32 @@ +{ + "name": "ep/ep-theme", + "description": "Theme package for Ep Reisen TYPO3 website", + "license": "GPL-2.0-or-later", + "type": "typo3-cms-extension", + "authors": [ + { + "name": "Björn Fromme", + "email": "fromme@dreipunktnull.com" + } + ], + "require": { + "php": "^8.2", + "b13/container": "^4.1", + "ep/ep-products": "@dev", + "typo3/cms-core": "^13.4", + "typo3/cms-extbase": "^13.4", + "typo3/cms-fluid": "^13.4", + "typo3/cms-fluid-styled-content": "^13.4", + "typo3/cms-frontend": "^13.4" + }, + "autoload": { + "psr-4": { + "EP\\EpTheme\\": "Classes/" + } + }, + "extra": { + "typo3/cms": { + "extension-key": "ep_theme" + } + } +} diff --git a/packages/ep_theme/ext_emconf.php b/packages/ep_theme/ext_emconf.php new file mode 100644 index 0000000..0f46223 --- /dev/null +++ b/packages/ep_theme/ext_emconf.php @@ -0,0 +1,21 @@ + 'E&P Theme', + 'description' => 'Theme package for Ep Reisen TYPO3 website', + 'category' => 'plugins', + 'author' => 'Björn Fromme', + 'author_email' => 'fromme@dreipunktnull.com', + 'state' => 'beta', + 'internal' => '', + 'uploadfolder' => true, + 'createDirs' => '', + 'dividers2tabs' => true, + 'clearCacheOnLoad' => 0, + 'version' => '3.0.0', + 'constraints' => [ + 'depends' => [], + 'conflicts' => [], + 'suggests' => [] + ], +]; diff --git a/packages/ep_theme/ext_icon.gif b/packages/ep_theme/ext_icon.gif new file mode 100644 index 0000000000000000000000000000000000000000..9fc0f1d07e14014fb1c8ac17b796a1b96534418c GIT binary patch literal 1425 zcmZ?wbhEHb6krfwxN6Dpd6w1RPftJ3wD>&3?Ca|2?+2QHo|^IR*Vk`ba{qn*@OhTy z-`6+3Eb{z$dgkv($Nv5L^6&TeUsu+;HPTl4<^{qt>O`j3;-K210M^X&Zhef9tU{r&s)_TP_>zwIdbG~MLO zLiazl zs47YguJQ{>uF6ifOi{A8Q?RM9s>m(KO)W`OsL0L9E4HezRZ2|BPfE1{vO&W7N(x{l zCE2!0jvxsIke$x?MX3s=dd9lR28MQ^Kd z=o{)8fV>1#=KlDb#X~hD#E>34K5C;EJ)Q4N-fSWElN%WDpv+sRbb^@l$uzQ zUlfv`p92fUfQ)w?G_W9=0WwQ;C72CPQCg-$LNEi7AdM>LcsI` zV!{(HkONQpsd>QkUIa|ojKJLX=l8FlKfZta`sMSdk00K@d;8|~tCugHKYRM*@uPti4*$!dV9LNIy>6iT3ec% z8XM~CYHO;iDl5v%N=u513Jdb{a&xk?GBeWCQd5$X5)DCA|t}XLPLUs0t5X0 ze0{vVJU!gqTwR=<93AZKY;CNqEG^8^sxfvt$+j&H=_&?2+fNf%F@EYK+_ zst9x9lU1@%ZYr*{aMB7>bS~{}wN#M~%(tA{((j~dXd%@zUqnRDQm%P!b6;M;+S2mA o1bt4KRCURWtentrVQozYzE}%Z4=*2i1r1C8z+ktl&W;S$00*5jivR!s literal 0 HcmV?d00001 diff --git a/packages/ep_theme/ext_localconf.php b/packages/ep_theme/ext_localconf.php new file mode 100644 index 0000000..453750a --- /dev/null +++ b/packages/ep_theme/ext_localconf.php @@ -0,0 +1,63 @@ + [ + 'TYPO3\\CMS\\Core\\Log\\Writer\\FileWriter' => [ + 'logFile' => \TYPO3\CMS\Core\Core\Environment::getVarPath() . '/log/bpn-api.log', + ], + ], + ]; + + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['ep_theme'] = + \EP\EpTheme\Hooks\Tcemain::class; + + $layout = 'cms/layout/class.tx_cms_layout.php'; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_country'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserCountryPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_region'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserRegionPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_city'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserCityPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_hotel'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserHotelPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_concept'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserConceptPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_product'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserProductPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['teaser_booking_link'] = + \EP\EpTheme\Hooks\PageLayoutView\TeaserBookingLinkPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['gmap'] = + \EP\EpTheme\Hooks\PageLayoutView\GmapPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['disturber'] = + \EP\EpTheme\Hooks\PageLayoutView\DisturberPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['disrupter'] = + \EP\EpTheme\Hooks\PageLayoutView\DisrupterPreviewRenderer::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$layout]['tt_content_drawItem']['lineup'] = + \EP\EpTheme\Hooks\PageLayoutView\LineupPreviewRenderer::class; + + $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'] = 'tx_eptheme_slides'; + + $GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['default'] = 'EXT:ep_theme/Configuration/RTE/Default.yaml'; + $GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['ep-reisen'] = 'EXT:ep_theme/Configuration/RTE/Default.yaml'; + $GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['ep-minimal'] = 'EXT:ep_theme/Configuration/RTE/Minimal.yaml'; + + $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['ep'] = ['EP\\EpTheme\\ViewHelpers']; + $GLOBALS['TYPO3_CONF_VARS']['MAIL']['layoutRootPaths'][700] = 'EXT:ep_theme/Resources/Private/Layouts/Email/'; + $GLOBALS['TYPO3_CONF_VARS']['MAIL']['templateRootPaths'][700] = 'EXT:ep_theme/Resources/Private/Templates/Email/'; + $GLOBALS['TYPO3_CONF_VARS']['MAIL']['format'] = 'html'; + + $metaTagManagerRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry::class); + $metaTagManagerRegistry->registerManager( + 'ep_opengraph', + \EP\EpTheme\MetaTag\OpenGraphMetaTagManager::class + ); +}; + +$boot(); +unset($boot); + diff --git a/packages/ep_theme/ext_tables.php b/packages/ep_theme/ext_tables.php new file mode 100644 index 0000000..7e4d164 --- /dev/null +++ b/packages/ep_theme/ext_tables.php @@ -0,0 +1,39 @@ +registerIcon( + 'content-ep_theme-ce', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:ep_theme/Resources/Public/images/logo.svg'] + ); + } + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule( + 'EP.EpTheme', + 'web', + 'management', + 'after:list', + [ + 'Backend' => 'listPages', + ], + [ + 'access' => 'user,group', + 'labels' => 'LLL:EXT:ep_theme/Resources/Private/Language/locallang_mod.xlf', + 'icon' => 'EXT:ep_products/Resources/Public/Icons/logo.svg' + ] + ); +}); diff --git a/packages/ep_theme/ext_tables.sql b/packages/ep_theme/ext_tables.sql new file mode 100644 index 0000000..98bb4f1 --- /dev/null +++ b/packages/ep_theme/ext_tables.sql @@ -0,0 +1,225 @@ +# noinspection SqlNoDataSourceInspectionForFile +# +# Table structure for table 'tx_eptheme_domain_model_slider' +# +CREATE TABLE tx_eptheme_domain_model_slide +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + headline varchar(255) DEFAULT '' NOT NULL, + subline varchar(255) DEFAULT '' NOT NULL, + button varchar(255) DEFAULT '' NOT NULL, + page_uid varchar(255) DEFAULT '' NOT NULL, + skipass tinyint(4) unsigned DEFAULT '1' NOT NULL, + image int(11) unsigned NOT NULL default '0', + page int(11) unsigned DEFAULT '0', + sorting int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_eptheme_domain_model_disrupter' +# +CREATE TABLE tx_eptheme_domain_model_disrupter +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + headline varchar(255) DEFAULT '' NOT NULL, + layout tinyint(4) unsigned DEFAULT '0' NOT NULL, + text text NOT NULL, + pi_flexform mediumtext NOT NULL, + image int(11) unsigned NOT NULL default '0', + tt_content int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +# +# Table structure for table 'tx_eptheme_domain_model_lineup' +# +CREATE TABLE tx_eptheme_domain_model_lineup +( + + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + name varchar(255) DEFAULT '' NOT NULL, + description text NOT NULL, + image int(11) unsigned NOT NULL default '0', + facebook_url varchar(255) DEFAULT '' NOT NULL, + twitter_url varchar(255) DEFAULT '' NOT NULL, + instagram_url varchar(255) DEFAULT '' NOT NULL, + youtube_url varchar(255) DEFAULT '' NOT NULL, + soundcloud_url varchar(255) DEFAULT '' NOT NULL, + sorting int(11) unsigned DEFAULT '0' NOT NULL, + tt_content int(11) unsigned DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) unsigned DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + starttime int(11) unsigned DEFAULT '0' NOT NULL, + endtime int(11) unsigned DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(255) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage int(11) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY t3ver_oid (t3ver_oid, t3ver_wsid), + KEY language (l10n_parent, sys_language_uid) + +); + +CREATE TABLE pages +( + tx_eptheme_context_pacode char(8) DEFAULT '', + tx_eptheme_context_country_code char(2) DEFAULT '', + tx_eptheme_context_country int(11) unsigned DEFAULT '0', + tx_eptheme_context_region int(11) unsigned DEFAULT '0', + tx_eptheme_context_concept int(11) unsigned DEFAULT '0', + tx_eptheme_context_product int(11) unsigned DEFAULT '0', + tx_eptheme_hide_searchbar tinyint(4) unsigned DEFAULT '0' NOT NULL, + tx_eptheme_new_tab tinyint(4) unsigned DEFAULT '0' NOT NULL, + tx_eptheme_bodyclass varchar(255) DEFAULT '' NOT NULL, + tx_eptheme_navitemclass varchar(255) DEFAULT '' NOT NULL, + tx_eptheme_slides int(11) unsigned DEFAULT '0', + tx_eptheme_badge int(11) unsigned DEFAULT '0' +); + +CREATE TABLE tt_content +( + button varchar(255) DEFAULT '' NOT NULL, + tx_eptheme_context_country int(11) unsigned DEFAULT '0', + tx_eptheme_context_region int(11) unsigned DEFAULT '0', + tx_eptheme_context_city int(11) unsigned DEFAULT '0', + tx_eptheme_context_hotel int(11) unsigned DEFAULT '0', + tx_eptheme_context_concept int(11) unsigned DEFAULT '0', + tx_eptheme_context_product int(11) unsigned DEFAULT '0', + tx_eptheme_context_date_from int(11) unsigned DEFAULT '0' NOT NULL, + tx_eptheme_context_date_to int(11) unsigned DEFAULT '0' NOT NULL, + tx_eptheme_lineup int(11) unsigned DEFAULT '0', + tx_eptheme_contact int(11) unsigned DEFAULT '0', + tx_eptheme_disrupter int(11) unsigned DEFAULT '0', + tx_eptheme_background tinyint(4) unsigned DEFAULT '0' NOT NULL +); + +CREATE TABLE tx_eptheme_ttcontent_tx_eptheme_disrupter_mm +( + uid_local int(11) DEFAULT '0' NOT NULL, + uid_foreign int(11) DEFAULT '0' NOT NULL, + sorting int(11) DEFAULT '0' NOT NULL, + + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +CREATE TABLE tx_eptheme_domain_model_addresscheck +( + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + tstamp int(11) DEFAULT '0' NOT NULL, + crdate int(11) DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + deleted tinyint(3) DEFAULT '0', + hidden tinyint(4) DEFAULT '0' NOT NULL, + + name tinytext, + first_name tinytext, + last_name tinytext, + customer_id varchar(255) DEFAULT '' NOT NULL, + title varchar(255) DEFAULT '' NOT NULL, + email varchar(255) DEFAULT '' NOT NULL, + address tinytext, + city varchar(255) DEFAULT '' NOT NULL, + zip varchar(20) DEFAULT '' NOT NULL, + country varchar(128) DEFAULT '' NOT NULL, + + no_postal tinyint(4) DEFAULT '0' NOT NULL, + no_email tinyint(4) DEFAULT '0' NOT NULL, + address_current tinyint(4) DEFAULT '0' NOT NULL, + + t3ver_oid int(11) DEFAULT '0' NOT NULL, + t3ver_id int(11) DEFAULT '0' NOT NULL, + t3ver_wsid int(11) DEFAULT '0' NOT NULL, + t3ver_label varchar(30) DEFAULT '' NOT NULL, + t3ver_state tinyint(4) DEFAULT '0' NOT NULL, + t3ver_stage tinyint(4) DEFAULT '0' NOT NULL, + t3ver_count int(11) DEFAULT '0' NOT NULL, + t3ver_tstamp int(11) DEFAULT '0' NOT NULL, + t3ver_move_id int(11) DEFAULT '0' NOT NULL, + t3_origuid int(11) DEFAULT '0' NOT NULL, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY pid (pid, email) +);