Compare commits

...
13 Commits
Author SHA1 Message Date
frommeandClaude Opus 5.5 f0b23b37b9 feat: the newsletter form submits with htmx and reports failed signups
The newsletter subscription plugin now sits in a form island like the
EXT:form forms: hx-boost posts to the page itself and only
#newsletter-island is swapped (validation errors, confirmation), with the
spinner overlay while the request runs. The id keeps htmx away from other
islands on the same page (both newsletter pages have hidden EXT:form
forms). Without JavaScript the form still posts normally.

The island markup moves from Form/Render.html into Partials/Form/Island.html,
shared by both; the form plugin's partial path is now Partials/ for that.
The comment about static pages is gone with it, static rendering is retired.

When the MyE&P API call fails, the result page shows an error with a link
back to the form instead of the thank-you message.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 09:17:16 +01:00
frommeandClaude Opus 5.5 88fac7313e fix: deploys reset OPcache before flushing the TYPO3 caches, as on TYPO3 10
After a deploy, pages showed unstyled content until the caches were
flushed by hand in the backend. Every vhost points at current/public
since 2026-10-24 and keeps running the previous release after the
switch; pages it renders after the deploy's flush reference that
release's hashed CSS/JS, which the new release does not have, and stay
in the page cache and as staticfilecache files.

As in TYPO3 10's deploy.php, cachetool resets OPcache right after the
switch (and after a rollback), before the flush. Its web adapter puts
the temp script into the shared typo3temp/assets, which every release
resolves to the same directory, and requests it through
www.ep-reisen.de. cachetool is pinned to 10.0.0 for PHP 8.5.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 08:20:21 +01:00
frommeandClaude Opus 5.5 de3bbbe2f5 feat: bring back the newsletter subscription plugin
The "Newsletter-/RA-Registrierung" plugin (epproducts_newsletter_subscription)
subscribes to the Mailjet lists through the MyE&P API and is still in use on
the newsletter, travel alert and travel for groups pages; retiring it in
49b96df was a mistake. Controller and templates come back from a46918d,
ported to TYPO3 13 (actions return responses). The DTO and the API client
calls were still in place. The "newsletter" result message plugin (index
action) is not part of this.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 07:58:04 +01:00
frommeandClaude Opus 5.5 b794257f61 chore: FlexForm select items in the TYPO3 13 format
The select items of twelve plugin FlexForms were still numbered
<numIndex index="0">/<numIndex index="1"> pairs, and the team members
FlexForm set enableMultiSelectFilterTextfield, which TYPO3 12 removed (the
filter field is always shown now). The core migrated both on the fly on
every request and logged a deprecation each time ("FlexFormTools did an
on-the-fly migration"). The items are converted to <label>/<value> with
Fractor (MigrateItemsIndexedKeysToAssociativeFractor) and the setting is
removed.

The parsed data structures are identical to what the on-the-fly migration
produced, so the backend forms and stored values stay the same.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 19:22:25 +01:00
frommeandClaude Opus 5.5 f4e024a9f8 chore: slim deploy.php down to what every deploy needs
Removed the one-off tasks of the first deploys, all done now: host:check
(host checks before the first deploy, including the static vhosts),
typo3:secrets:init and typo3:secrets:check (secrets.php is filled in), and
typo3:settings:init (the shared settings.php exists since the deploy after
3f88229). Also removed what the Deployer 8 recipe already provides with the
same result: typo3/public_dir and bin/typo3 (read from composer.json),
typo3:extension:setup and the unlock after a failed deploy. The build always
runs through ddev exec, as deploys run on the host.

The resolved configuration (rsync excludes, flags and filter, shared and
writable directories, build path) and the task order are unchanged.
contrib/rsync.php replaces the recipe's rsync settings, so the recipe's
var/ exclude never applied: var/labels is deployed with the release, as
before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 18:32:21 +01:00
frommeandClaude Opus 5.5 73fe876c78 fix: the n8n chat and the CleverPush service worker are served again
The ep-reisen footer loads the chat widget from /chat.bundle.es.js and
/chat.styles.css (@n8n/chat 0.65.0), and browsers subscribed to push
notifications update their service worker from /cleverpush-worker.js
(manifest: /cleverpush-manifest.json). These files were in the TYPO3 10
docroot but not in this repository, so since the switch to TYPO3 13 all
four returned 404: no chat button, and push subscriptions could no longer
update their worker.

public/* is ignored except for listed files, so they are added as
exceptions; a service worker has to be served from the root to control
the whole site.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 18:18:00 +01:00
frommeandClaude Opus 5.5 c7f6a6ec44 chore: remove the last legacy favicons from the theme
ski-boarderweek, snowzone and ser have a realfavicongenerator set in
fileadmin now, and their templates no longer set page.shortcutIcon, so
favicon_sbw.ico, favicon_sz.ico and favicon_ser.ico are unused.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 18:02:42 +01:00
frommeandClaude Opus 5.5 8302214657 feat: ICO and web manifest files can be uploaded to fileadmin
TYPO3 13 only accepts uploads whose extension is in textfile_ext,
mediafile_ext or miscfile_ext (security.system.enforceAllowedFileExtensions),
so the favicon.ico and site.webmanifest of a realfavicongenerator set were
rejected ("Resource consistency check failed"). Both are appended to
miscfile_ext in additional.php, which is deployed with the code;
settings.php, which production leads, stays untouched.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 17:34:20 +01:00
frommeandClaude Opus 5.5 1d7fadbd19 feat: favicons as a realfavicongenerator set per site, like ep-reisen and snowbreak
The theme registered 26 icons (16 to 196 px PNGs, a GIF, eleven Apple touch
icons) that current browsers no longer need. It now renders the five tags of
the set realfavicongenerator.net generates (96 px PNG, SVG, ICO, 180 px
Apple touch icon, web manifest) plus apple-mobile-web-app-title, from two
constants: faviconPath, the folder in fileadmin the set was uploaded to
(default: the E&P set), and faviconAppTitle.

The ICO also matches the favicon parser of yoast's snippet preview now
(rel="shortcut icon" directly followed by href), which missed the old tags
and fell back to a /favicon.ico none of the sites has.

cllt and alvac, which showed the theme's E&P icon, get the E&P set. Sites
whose own template sets page.shortcutIcon and removes headerData.333
(ski-boarderweek, snowzone, ser) keep their icon until they have a set; the
remaining ICOs of these three stay until then. ep-reisen and snowbreak still
override the tags with the same markup in their templates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 15:19:05 +01:00
frommeandClaude Opus 5.5 c0c81317aa chore: remove the unused myEP API settings from the theme
The app config JSON carried "myEpApiBaseUrl" from the TypoScript setting
myEpApiEndpointUrl, which only the alvac and cllt sys_template records set:
the other sites rendered the unresolved "{$plugin.tx_eptheme.settings...}"
into the page. No JavaScript reads the key (only paCode and externalDomains
are used), and the MyE&P API client takes its URL, credentials and timeout
from the ep_products extension configuration, so the theme's
myEpApiEndpointUrl, myEpApiToken and myEpApiTimeout settings had no reader.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 15:07:13 +01:00
frommeandClaude Opus 5.5 32c0c89785 feat: theme constant googleSiteVerification for the Google site verification
The verification code of alvac and cllt came from EXT:metaseo, which is not
installed in TYPO3 13, so their google-site-verification meta tag was
missing. The theme renders the new constant as
page.meta.google-site-verification; empty, it renders no tag. Sites whose
own setup sets the tag (ep-reisen, ski-boarderweek, snowbreak, snowzone, ser)
keep their value.

.ddev/plans/sql/2026-10-25-metaseo-sourceopt.sql renames the metaseo
constants to the new one after the deploy, removes the remaining metaseo
and sourceopt leftovers from the sys_template records, and sets the core
no_index/no_follow flags on the booking links (page 634 and subpages),
which metaseo had kept out of search engines.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 14:56:46 +01:00
frommeandClaude Opus 5.5 4ca38821fb fix: the backend may load images from cdn.ep-reisen.de
TYPO3 13 enforces a Content Security Policy in the backend whose img-src
only allows the backend's own host, data: URIs and the YouTube/Vimeo CDNs.
The frontend references its favicons on the CDN, so yoast's snippet preview
showed a broken favicon (CSP report: img-src blocked
https://cdn.ep-reisen.de/fileadmin/user_upload/favicon/ep/favicon.ico).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 14:53:01 +01:00
frommeandClaude Opus 5.5 b39f17886f feat: bring back EXT:yoast_seo
Editors need its focus keyword and readability analysis in the page form.
Version 12.1 supports TYPO3 13.4 and keeps the existing tx_yoastseo_* data
of the TYPO3 10 database.

The deploy's extension:setup adds its new pages columns and tables. After
the deploy, run the upgrade wizard yoastDashboardWidgetMigrate (it moves the
yoast "pages without meta description" dashboard widget to the core one).

In the frontend, yoast adds WebSite and BreadcrumbList JSON-LD to every page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 14:47:49 +01:00
62 changed files with 30923 additions and 311 deletions

No files matched your search

+5
View File
@@ -6,5 +6,10 @@ nbproject
/vendor
/public/*
!/public/.htaccess
# Third-party files that have to be served from the site root
!/public/chat.bundle.es.js
!/public/chat.styles.css
!/public/cleverpush-manifest.json
!/public/cleverpush-worker.js
/node_modules
/packages/ep_theme/Resources/Public/
+2 -1
View File
@@ -49,7 +49,8 @@
"typo3/cms-sys-note": "^13.4",
"typo3/cms-tstemplate": "^13.4",
"typo3/cms-viewpage": "^13.4",
"typo3/cms-webhooks": "^13.4"
"typo3/cms-webhooks": "^13.4",
"yoast-seo-for-typo3/yoast_seo": "^12.1"
},
"repositories": [
{
Generated
+163 -1
View File
@@ -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": "f1134476988f3bf5d2a3a74092cdb285",
"content-hash": "c1db37c5c31e87b85ee7db5788ca99c8",
"packages": [
{
"name": "b13/container",
@@ -2055,6 +2055,79 @@
},
"time": "2026-08-18T06:18:41+00:00"
},
{
"name": "maxserv/frontend-request",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/MaxServ/frontend-request.git",
"reference": "527296df01a6370bf3ad40d6514a3538129f1bc6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MaxServ/frontend-request/zipball/527296df01a6370bf3ad40d6514a3538129f1bc6",
"reference": "527296df01a6370bf3ad40d6514a3538129f1bc6",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^8.2",
"typo3/cms-backend": "^12.4.25 || ^13.4 || ^14.1",
"typo3/cms-core": "^12.4.25 || ^13.4 || ^14.1",
"typo3/cms-frontend": "^12.4.25 || ^13.4 || ^14.1"
},
"replace": {
"typo3-ter/frontend-request": "self.version"
},
"require-dev": {
"composer/class-map-generator": "^1.3.4",
"dg/bypass-finals": "^1.8",
"ergebnis/composer-normalize": "^2.43",
"friendsofphp/php-cs-fixer": "^3.60.0",
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.12 || ^2.1",
"phpunit/phpunit": "^10.1 || ^11.2.5",
"saschaegerer/phpstan-typo3": "^1.10 || ^2.1 || ^3.0.1",
"typo3/coding-standards": "^0.7.1 || ^0.8.0",
"typo3/testing-framework": "^7.1.0 || ^8.2.0 || ^9.5.0"
},
"type": "typo3-cms-extension",
"extra": {
"typo3/cms": {
"web-dir": ".Build/public",
"extension-key": "frontend_request"
}
},
"autoload": {
"psr-4": {
"MaxServ\\FrontendRequest\\": "Classes/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0-or-later"
],
"authors": [
{
"name": "Team MaxServ",
"email": "[email protected]",
"homepage": "https://maxserv.com"
}
],
"description": "Helper extension to make a frontend request from the backend of TYPO3 using javascript.",
"homepage": "https://maxserv.com",
"keywords": [
"MaxServ",
"TYPO3 CMS"
],
"support": {
"docs": "https://docs.typo3.org/p/maxserv/frontend_request/main/en-us/",
"issues": "https://github.com/MaxServ/frontend-request/issues",
"source": "https://github.com/MaxServ/frontend-request"
},
"time": "2026-04-30T08:31:29+00:00"
},
{
"name": "nikic/php-parser",
"version": "v5.9.0",
@@ -8855,6 +8928,95 @@
"source": "https://github.com/webmozarts/assert/tree/2.5.0"
},
"time": "2026-10-05T23:14:19+00:00"
},
{
"name": "yoast-seo-for-typo3/yoast_seo",
"version": "12.1.2",
"source": {
"type": "git",
"url": "https://github.com/Yoast/Yoast-SEO-for-TYPO3.git",
"reference": "1287de1607a20976390a5adaaa6db57f2301a4c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Yoast/Yoast-SEO-for-TYPO3/zipball/1287de1607a20976390a5adaaa6db57f2301a4c5",
"reference": "1287de1607a20976390a5adaaa6db57f2301a4c5",
"shasum": ""
},
"require": {
"ext-json": "*",
"maxserv/frontend-request": "^2.0",
"php": "^8.2",
"typo3/cms-backend": "^12.4.25 || ^13.4 || ^14.3",
"typo3/cms-core": "^12.4.25 || ^13.4 || ^14.3",
"typo3/cms-extbase": "^12.4.25 || ^13.4 || ^14.3",
"typo3/cms-fluid": "^12.4.25 || ^13.4 || ^14.3",
"typo3/cms-frontend": "^12.4.25 || ^13.4 || ^14.3",
"typo3/cms-install": "^12.4.25 || ^13.4 || ^14.3",
"typo3/cms-seo": "^12.4.25 || ^13.4 || ^14.3"
},
"replace": {
"typo3-ter/yoast-seo": "self.version"
},
"require-dev": {
"composer/class-map-generator": "^1.3.4",
"dg/bypass-finals": "^1.8",
"ergebnis/composer-normalize": "^2.43",
"friendsofphp/php-cs-fixer": "^3.60.0",
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.12 || ^2.1",
"phpunit/phpunit": "^10.1 || ^11.2.5",
"saschaegerer/phpstan-typo3": "^1.10 || ^2.1 || ^3.0.1",
"typo3/coding-standards": "^0.7.1 || ^0.8.0",
"typo3/tailor": "^1.7",
"typo3/testing-framework": "^7.1.0 || ^8.2.0 || ^9.5.0"
},
"suggest": {
"typo3/cms-dashboard": "Display Yoast SEO widgets within the Dashboard of TYPO3"
},
"type": "typo3-cms-extension",
"extra": {
"typo3/cms": {
"web-dir": ".Build/public",
"extension-key": "yoast_seo"
}
},
"autoload": {
"psr-4": {
"YoastSeoForTypo3\\YoastSeo\\": "Classes/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0-or-later"
],
"authors": [
{
"name": "Team Yoast",
"email": "[email protected]",
"homepage": "https://yoast.com"
},
{
"name": "Team MaxServ",
"email": "[email protected]",
"homepage": "https://maxserv.com"
}
],
"description": "Yoast SEO for TYPO3",
"homepage": "https://yoast.com",
"keywords": [
"MaxServ",
"TYPO3 CMS",
"seo",
"yoast"
],
"support": {
"docs": "https://docs.typo3.org/p/yoast-seo-for-typo3/yoast_seo/main/en-us/",
"issues": "https://github.com/Yoast/Yoast-SEO-for-TYPO3/issues",
"source": "https://github.com/Yoast/Yoast-SEO-for-TYPO3"
},
"time": "2026-07-08T13:20:01+00:00"
}
],
"packages-dev": [
+4
View File
@@ -94,6 +94,10 @@ $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['rootline']
];
unset($redisOptions);
// Favicon sets from realfavicongenerator.net (fileadmin/user_upload/favicon/<site>/) contain an ICO
// and a web manifest, which TYPO3 13 rejects on upload unless an extension list allows them
$GLOBALS['TYPO3_CONF_VARS']['SYS']['miscfile_ext'] .= ',ico,webmanifest';
// Deployer creates an empty secrets.php if none exists yet: only an array counts
$secrets = is_file(__DIR__ . '/secrets.php') ? require __DIR__ . '/secrets.php' : null;
if (is_array($secrets)) {
+34 -124
View File
@@ -2,29 +2,16 @@
namespace Deployer;
// Run on the host (Deployer 8, PHP >= 8.3): dep deploy prod. The SSH keys stay on the host; the
// build runs inside DDEV, so Composer and Node are the project's versions.
// Prerequisites on the host: see .ddev/plans/TYPO3-13-GO-LIVE.md (PHP 8.5.12, database, secrets.php)
require 'recipe/typo3.php';
require 'contrib/rsync.php';
require 'contrib/cachetool.php';
set('cachetool_url', 'https://github.com/gordalina/cachetool/releases/download/10.0.0/cachetool.phar');
set('application', 'ep-reisen-typo3-13');
// Defined here rather than taken from recipe/typo3.php, whose task and setting names differ between
// Deployer versions (a globally installed "dep" may not be the project's vendor/bin/dep 8)
set('typo3/public_dir', 'public');
set('bin/typo3', 'vendor/bin/typo3');
set('keep_releases', 3);
// The release is built locally from the committed state, so nothing uncommitted is ever deployed
// and the host needs neither Composer nor Node
set('build_dir', __DIR__ . '/var/deploy-build');
set('use_rsync', true);
set('rsync_src', '{{build_dir}}');
// fileadmin is the production fileadmin of the TYPO3 10 release: it is the CDN origin's fileadmin
// (static/ep-reisen/fileadmin links there too), and TYPO3 13 writes its processed images into it
// fileadmin of the TYPO3 10 release until it is moved into shared/ (plan 4a, tidy up step 2)
set('fileadmin_path', '/usr/home/eptypo3/public_html/ep-reisen-typo3/shared/public/fileadmin');
set('shared_dirs', [
@@ -36,10 +23,7 @@ set('shared_dirs', [
// Baseline of the MyE&P import: without it every deploy would trigger a full re-import
'var/transient/ep_products_import',
]);
// settings.php is shared as LocalConfiguration.php was on TYPO3 10: production is leading, so what
// is changed in the backend (extension configuration, install tool) survives deploys and rollbacks.
// It is not in git (encryptionKey, install tool password): configuration changes for production are
// made there, in the backend or by hand on the shared file.
// Not in git, production is leading (encryptionKey, install tool, backend changes)
set('shared_files', [
'config/system/secrets.php',
'config/system/settings.php',
@@ -53,6 +37,13 @@ set('writable_dirs', [
]);
set('writable_mode', 'chmod');
// The release is built from the committed state, so nothing uncommitted and no dev dependencies go live
set('build_dir', __DIR__ . '/var/deploy-build');
set('use_rsync', true);
set('rsync_src', '{{build_dir}}');
// contrib/rsync.php replaces the TYPO3 recipe's rsync settings, so its exclude list (which has var/)
// does not apply: the language packs in var/labels are deployed with the release
$rsync = get('rsync');
$rsync['exclude'] = array_merge($rsync['exclude'], [
'/{{typo3/public_dir}}/fileadmin',
@@ -68,7 +59,7 @@ $rsync['exclude'] = array_merge($rsync['exclude'], [
'/package-lock.json',
'/.nvmrc',
]);
// The build directory has no .gitignore filtering to do, and the filter would drop the built assets
// No .gitignore filter: it would drop the built assets
$rsync['filter'] = [];
// -t preserves mtimes: without it every file lands with a fresh timestamp, so unchanged
// files get a new Last-Modified on every deploy and every conditional request revalidates
@@ -83,124 +74,45 @@ host('prod')
->setForwardAgent(true)
->setSshMultiplexing(true)
->setDeployPath('/usr/home/eptypo3/public_html/{{application}}')
// Verify on the host once Hetzner has installed it
->set('bin/php', '/usr/bin/php85')
->set('http_user', 'eptypo3')
// The temp script goes into the shared typo3temp/assets, which resolves to the same directory from
// every release: PHP workers with a stale realpath cache still map "current" to the previous release
// and would 404 on a script placed inside the new one.
->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{deploy_path}}/shared/{{typo3/public_dir}}/typo3temp/assets/ --web-url=https://www.ep-reisen.de/typo3temp/assets')
;
desc('Build the release locally: committed state, production dependencies, theme assets');
task('build', function () {
// Inside DDEV directly, on the host through "ddev exec" (paths relative to the project root)
$inDdev = getenv('IS_DDEV_PROJECT') === 'true';
$exec = fn(string $command) => runLocally($inDdev
? $command
: 'ddev exec bash -c ' . escapeshellarg($command));
task('deploy:build', function () {
runLocally('rm -rf {{build_dir}} && mkdir -p {{build_dir}}');
runLocally('git archive HEAD | tar -x -C {{build_dir}}');
// Assets first: Composer only publishes an extension's Resources/Public as public/_assets/<hash>
// if the directory exists when it runs, and the theme's is created by the build
$exec('cd var/deploy-build && npm ci --no-audit --no-fund && npm run build');
$exec('cd var/deploy-build && composer install --no-dev --prefer-dist --no-progress --no-interaction --optimize-autoloader');
runLocally('test -L {{build_dir}}/public/_assets/' . md5('/vendor/ep/ep-theme/') . ' && test -f {{build_dir}}/packages/ep_theme/Resources/Public/entrypoints.json');
// Assets first: Composer only publishes the theme's Resources/Public to public/_assets
// if the directory exists when it runs, and the asset build creates it
runLocally('ddev exec bash -c "cd var/deploy-build && npm ci --no-audit --no-fund && npm run build"');
runLocally('ddev exec bash -c "cd var/deploy-build && composer install --no-dev --prefer-dist --no-progress --no-interaction --optimize-autoloader"');
})->once();
desc('Read-only checks of the host before the first deploy (see .ddev/plans/TYPO3-13-GO-LIVE.md)');
task('host:check', function () {
$checks = [
'PHP CLI 8.5.12+' => '{{bin/php}} -r \'exit(version_compare(PHP_VERSION, "8.5.12", ">=") ? 0 : 1);\' && {{bin/php}} -v | head -1',
'PHP extensions' => '{{bin/php}} -r \'$m = array_diff(["mysqli", "pdo_mysql", "intl", "mbstring", "xml", "zip", "gd", "fileinfo", "openssl", "curl", "zlib"], get_loaded_extensions()); echo $m ? "missing: " . implode(", ", $m) : "ok"; exit($m ? 1 : 0);\'',
'image processor (GraphicsMagick)' => 'test -x /usr/bin/gm && /usr/bin/gm version | head -1',
'production fileadmin' => 'test -d {{fileadmin_path}} && du -sh {{fileadmin_path}} 2>/dev/null | cut -f1',
'static/ep-reisen/fileadmin links there' => 'test "$(readlink -f /usr/home/eptypo3/public_html/static/ep-reisen/fileadmin)" = "$(readlink -f {{fileadmin_path}})" && echo yes',
'free disk space' => 'df -h /usr/home/eptypo3 | tail -1',
'secrets.php in place' => 'test -s {{deploy_path}}/shared/config/system/secrets.php && {{bin/php}} -r \'exit(is_array(require "{{deploy_path}}/shared/config/system/secrets.php") ? 0 : 1);\' && echo yes',
'MyE&P reachable' => 'curl -s -o /dev/null -w "%{http_code}" --max-time 10 https://my.ep-reisen.de/api/last-update',
];
$failed = false;
foreach ($checks as $label => $command) {
try {
writeln(sprintf('<info>✔</info> %s: %s', $label, trim(run($command))));
} catch (\Throwable $e) {
writeln(sprintf('<error>✘</error> %s', $label));
$failed = true;
}
}
writeln('Not checkable from the CLI: open_basedir and PHP 8.5.12 (FPM) of the static vhosts, see the go-live plan.');
if ($failed) {
throw new \RuntimeException('Host checks failed');
}
task('typo3:fileadmin', function () {
// Relative (-r), like Deployer's shared dirs: the vhosts answer 403 for an absolute symlink here
run('ln -sfnr {{fileadmin_path}} {{release_path}}/{{typo3/public_dir}}/fileadmin');
});
desc('Put the secrets.php template into shared/ unless a secrets.php is there already (never overwrites)');
task('typo3:secrets:init', function () {
$target = '{{deploy_path}}/shared/config/system/secrets.php';
if (test("[ -s $target ]")) {
return;
}
run('mkdir -p {{deploy_path}}/shared/config/system');
upload(__DIR__ . '/config/system/secrets.example.php', $target);
run("chmod 600 $target");
writeln('<comment>Created ' . parse($target) . ' from the template: fill it in, then deploy again.</comment>');
});
desc('Seed the shared settings.php from the live release unless it is there already (never overwrites)');
task('typo3:settings:init', function () {
$target = '{{deploy_path}}/shared/config/system/settings.php';
if (test("[ -s $target ]")) {
return;
}
// deploy:shared would seed it from the new release, i.e. from git, and drop production's settings
if (!test('[ -f {{deploy_path}}/current/config/system/settings.php ]')) {
return;
}
run('mkdir -p {{deploy_path}}/shared/config/system');
run("cp -L {{deploy_path}}/current/config/system/settings.php $target");
writeln('<comment>Created ' . parse($target) . ' from the live release.</comment>');
});
desc('Stop before the database is touched while secrets.php still lacks the database connection');
task('typo3:secrets:check', function () {
$secrets = '{{deploy_path}}/shared/config/system/secrets.php';
$filled = run("{{bin/php}} -r '\$s = require \"$secrets\"; echo is_array(\$s) && (\$s[\"DB\"][\"Connections\"][\"Default\"][\"dbname\"] ?? \"\") !== \"\" ? \"yes\" : \"no\";'");
if ($filled !== 'yes') {
throw new \RuntimeException(parse("$secrets has no database connection yet: fill it in, then deploy again. Nothing went live."));
}
});
desc('TYPO3 - Set up all extensions (database schema: adds and changes only)');
task('typo3:extension:setup', function () {
run('{{bin/php}} {{release_path}}/{{bin/typo3}} extension:setup');
});
desc('TYPO3 - Clear all caches');
// current_path instead of the recipe's release_path, so it also works after a rollback
task('typo3:cache:flush', function () {
run('{{bin/php}} {{current_path}}/{{bin/typo3}} cache:flush');
});
desc('Link the production fileadmin into the release');
task('typo3:fileadmin', function () {
run('test -d {{fileadmin_path}}');
// Relative (-r), like Deployer's shared dirs: the vhosts answer 403 for an absolute symlink here
run('ln -sfnr {{fileadmin_path}} {{release_path}}/{{typo3/public_dir}}/fileadmin');
run('test -d {{release_path}}/{{typo3/public_dir}}/fileadmin/user_upload');
});
desc('Deploys TYPO3 13');
task('deploy', [
'deploy:info',
// Build first, so a failing build aborts before the server is touched at all.
'build',
// Build locally first, so a failing build aborts before the server is touched at all.
'deploy:build',
'deploy:setup',
'deploy:lock',
'deploy:release',
'rsync',
'typo3:secrets:init',
'typo3:settings:init',
'deploy:shared',
'typo3:fileadmin',
'deploy:writable',
'typo3:secrets:check',
// The schema has to exist before the new code starts serving traffic (adds and changes only).
// The schema has to exist before the new code starts serving traffic.
'typo3:extension:setup',
// deploy:publish == deploy:symlink, deploy:unlock, deploy:cleanup, deploy:success
'deploy:publish',
@@ -209,12 +121,10 @@ task('deploy', [
// Caches belong to the release that goes live, so they are cleared after the symlink flip. Hooking
// into deploy:symlink rather than appending to the list above keeps them inside deploy:publish, ahead
// of deploy:unlock and deploy:success - a failing cache task must not follow a declared success.
// No OPcache reset (cachetool): its web adapter would need PHP under a host's typo3temp. Vhosts that
// point at current/public keep running the previous release until PHP's path cache expires (~2 min);
// pages it renders in that window stay in the page cache until the next flush.
// OPcache first: until it is reset the vhosts keep running the previous release, and the pages it
// renders (with its hashed CSS/JS) would outlive the flush.
after('deploy:symlink', 'cachetool:clear:opcache');
after('deploy:symlink', 'typo3:cache:flush');
// A rollback switches the code back; pages cached by the other release must not survive it
// Pages cached by the other release must not survive a rollback
after('rollback', 'cachetool:clear:opcache');
after('rollback', 'typo3:cache:flush');
after('deploy:failed', 'deploy:unlock');
@@ -0,0 +1,79 @@
<?php
namespace EP\EpProducts\Controller;
/***************************************************************
*
* Copyright notice
*
* (c) 2016 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS 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 Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class NewsletterController extends ActionController
{
private ApiClient $apiClient;
public function __construct(ApiClient $apiClient)
{
$this->apiClient = $apiClient;
}
public function subscriptionFormAction(?NewsletterSubscriptionRequest $subscriptionRequest = null): ResponseInterface
{
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);
return $this->htmlResponse();
}
public function subscriptionFormSubmitAction(NewsletterSubscriptionRequest $subscriptionRequest): ResponseInterface
{
try {
$result = $this->apiClient->subscribeToNewsletters($subscriptionRequest);
$result['success'] = true;
} catch (ApiException $e) {
$result = [
'success' => false,
'message' => $e->getMessage(),
];
}
$this->view->assign('result', $result);
return $this->htmlResponse();
}
}
@@ -19,8 +19,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0"/>
<numIndex index="1">0</numIndex>
<label/>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -37,8 +37,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0"/>
<numIndex index="1">0</numIndex>
<label/>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -53,12 +53,12 @@
<renderType>selectSingle</renderType>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Winter</numIndex>
<numIndex index="1">w</numIndex>
<label>Winter</label>
<value>w</value>
</numIndex>
<numIndex index="1" type="array">
<numIndex index="0">Sommer</numIndex>
<numIndex index="1">s</numIndex>
<label>Sommer</label>
<value>s</value>
</numIndex>
</items>
<minitems>1</minitems>
@@ -19,8 +19,8 @@
<foreign_table_where>AND tx_epproducts_domain_model_concept.hidden = 0 AND tx_epproducts_domain_model_concept.deleted = 0 ORDER BY tx_epproducts_domain_model_concept.name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Alle</numIndex>
<numIndex index="1">0</numIndex>
<label>Alle</label>
<value>0</value>
</numIndex>
</items>
<minitems>1</minitems>
@@ -19,8 +19,8 @@
<foreign_table_where>AND tx_epproducts_domain_model_country.hidden = 0 AND tx_epproducts_domain_model_country.deleted = 0 ORDER BY tx_epproducts_domain_model_country.name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0"/>
<numIndex index="1">0</numIndex>
<label/>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -19,8 +19,8 @@
<foreign_table_where>AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Alle</numIndex>
<numIndex index="1">0</numIndex>
<label>Alle</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -31,8 +31,8 @@
<foreign_table_where>AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Alle</numIndex>
<numIndex index="1">0</numIndex>
<label>Alle</label>
<value>0</value>
</numIndex>
</items>
<minitems>1</minitems>
@@ -17,16 +17,16 @@
<renderType>selectSingle</renderType>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">vierspaltig</numIndex>
<numIndex index="1">4</numIndex>
<label>vierspaltig</label>
<value>4</value>
</numIndex>
<numIndex index="1" type="array">
<numIndex index="0">dreispaltig</numIndex>
<numIndex index="1">3</numIndex>
<label>dreispaltig</label>
<value>3</value>
</numIndex>
<numIndex index="2" type="array">
<numIndex index="0">zweispaltig</numIndex>
<numIndex index="1">2</numIndex>
<label>zweispaltig</label>
<value>2</value>
</numIndex>
</items>
<minitems>1</minitems>
@@ -44,8 +44,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -63,8 +63,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -82,8 +82,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -100,8 +100,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -118,8 +118,8 @@
<foreign_table_where>ORDER BY name_internal</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -134,28 +134,28 @@
<renderType>selectSingle</renderType>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Alle</numIndex>
<numIndex index="1">0</numIndex>
<label>Alle</label>
<value>0</value>
</numIndex>
<numIndex index="1" type="array">
<numIndex index="0">Standard</numIndex>
<numIndex index="1">1</numIndex>
<label>Standard</label>
<value>1</value>
</numIndex>
<numIndex index="2" type="array">
<numIndex index="0">Basic</numIndex>
<numIndex index="1">5</numIndex>
<label>Basic</label>
<value>5</value>
</numIndex>
<numIndex index="3" type="array">
<numIndex index="0">Komfort</numIndex>
<numIndex index="1">2</numIndex>
<label>Komfort</label>
<value>2</value>
</numIndex>
<numIndex index="4" type="array">
<numIndex index="0">Deluxe</numIndex>
<numIndex index="1">3</numIndex>
<label>Deluxe</label>
<value>3</value>
</numIndex>
<numIndex index="5" type="array">
<numIndex index="0">Superdeluxe</numIndex>
<numIndex index="1">4</numIndex>
<label>Superdeluxe</label>
<value>4</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -169,24 +169,24 @@
<renderType>selectCheckBox</renderType>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Ferienwohnung</numIndex>
<numIndex index="1">1</numIndex>
<label>Ferienwohnung</label>
<value>1</value>
</numIndex>
<numIndex index="1" type="array">
<numIndex index="0">Sportclub</numIndex>
<numIndex index="1">2</numIndex>
<label>Sportclub</label>
<value>2</value>
</numIndex>
<numIndex index="2" type="array">
<numIndex index="0">Apartment</numIndex>
<numIndex index="1">3</numIndex>
<label>Apartment</label>
<value>3</value>
</numIndex>
<numIndex index="3" type="array">
<numIndex index="0">G&#xE4;sterhaus</numIndex>
<numIndex index="1">4</numIndex>
<label>G&#xE4;sterhaus</label>
<value>4</value>
</numIndex>
<numIndex index="4" type="array">
<numIndex index="0">Pension</numIndex>
<numIndex index="1">5</numIndex>
<label>Pension</label>
<value>5</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -17,16 +17,16 @@
<renderType>selectSingle</renderType>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">vierspaltig</numIndex>
<numIndex index="1">4</numIndex>
<label>vierspaltig</label>
<value>4</value>
</numIndex>
<numIndex index="1" type="array">
<numIndex index="0">dreispaltig</numIndex>
<numIndex index="1">3</numIndex>
<label>dreispaltig</label>
<value>3</value>
</numIndex>
<numIndex index="2" type="array">
<numIndex index="0">zweispaltig</numIndex>
<numIndex index="1">2</numIndex>
<label>zweispaltig</label>
<value>2</value>
</numIndex>
</items>
<minitems>1</minitems>
@@ -44,8 +44,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -63,8 +63,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -81,8 +81,8 @@
<foreign_table_where>ORDER BY name_internal</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -97,28 +97,28 @@
<renderType>selectSingle</renderType>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Alle</numIndex>
<numIndex index="1">0</numIndex>
<label>Alle</label>
<value>0</value>
</numIndex>
<numIndex index="1" type="array">
<numIndex index="0">Standard</numIndex>
<numIndex index="1">1</numIndex>
<label>Standard</label>
<value>1</value>
</numIndex>
<numIndex index="2" type="array">
<numIndex index="0">Basic</numIndex>
<numIndex index="1">5</numIndex>
<label>Basic</label>
<value>5</value>
</numIndex>
<numIndex index="3" type="array">
<numIndex index="0">Komfort</numIndex>
<numIndex index="1">2</numIndex>
<label>Komfort</label>
<value>2</value>
</numIndex>
<numIndex index="4" type="array">
<numIndex index="0">Deluxe</numIndex>
<numIndex index="1">3</numIndex>
<label>Deluxe</label>
<value>3</value>
</numIndex>
<numIndex index="5" type="array">
<numIndex index="0">Superdeluxe</numIndex>
<numIndex index="1">4</numIndex>
<label>Superdeluxe</label>
<value>4</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -132,24 +132,24 @@
<renderType>selectCheckBox</renderType>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Ferienwohnung</numIndex>
<numIndex index="1">1</numIndex>
<label>Ferienwohnung</label>
<value>1</value>
</numIndex>
<numIndex index="1" type="array">
<numIndex index="0">Sportclub</numIndex>
<numIndex index="1">2</numIndex>
<label>Sportclub</label>
<value>2</value>
</numIndex>
<numIndex index="2" type="array">
<numIndex index="0">Apartment</numIndex>
<numIndex index="1">3</numIndex>
<label>Apartment</label>
<value>3</value>
</numIndex>
<numIndex index="3" type="array">
<numIndex index="0">G&#xE4;sterhaus</numIndex>
<numIndex index="1">4</numIndex>
<label>G&#xE4;sterhaus</label>
<value>4</value>
</numIndex>
<numIndex index="4" type="array">
<numIndex index="0">Pension</numIndex>
<numIndex index="1">5</numIndex>
<label>Pension</label>
<value>5</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -18,8 +18,8 @@
<default>Pricetable->event</default>
<items type="array">
<numIndex index="1" type="array">
<numIndex index="0">Preistabelle Eventreise</numIndex>
<numIndex index="1">Pricetable->event</numIndex>
<label>Preistabelle Eventreise</label>
<value>Pricetable->event</value>
</numIndex>
</items>
</config>
@@ -19,8 +19,8 @@
<foreign_table_where>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</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Alle</numIndex>
<numIndex index="1">0</numIndex>
<label>Alle</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -37,8 +37,8 @@
<foreign_table_where>AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Alle</numIndex>
<numIndex index="1">0</numIndex>
<label>Alle</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -31,8 +31,8 @@
<foreign_table_where>AND tx_epproducts_domain_model_hotel.hidden = 0 AND tx_epproducts_domain_model_hotel.deleted = 0 ORDER BY tx_epproducts_domain_model_hotel.name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Alle</numIndex>
<numIndex index="1">0</numIndex>
<label>Alle</label>
<value>0</value>
</numIndex>
</items>
<minitems>1</minitems>
@@ -18,12 +18,12 @@
<default>Product->teasergroup</default>
<items type="array">
<numIndex index="1" type="array">
<numIndex index="0">Teasergruppe</numIndex>
<numIndex index="1">Product->teasergroup</numIndex>
<label>Teasergruppe</label>
<value>Product->teasergroup</value>
</numIndex>
<numIndex index="2" type="array">
<numIndex index="0">Einzelteaser</numIndex>
<numIndex index="1">Product->teaser</numIndex>
<label>Einzelteaser</label>
<value>Product->teaser</value>
</numIndex>
</items>
</config>
@@ -40,8 +40,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -60,8 +60,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -80,8 +80,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Keine Zuordnung</numIndex>
<numIndex index="1">0</numIndex>
<label>Keine Zuordnung</label>
<value>0</value>
</numIndex>
</items>
<size>5</size>
@@ -98,8 +98,8 @@
<foreign_table_where>ORDER BY name</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Alle</numIndex>
<numIndex index="1">0</numIndex>
<label>Alle</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -147,8 +147,8 @@
<foreign_table_where>ORDER BY name_internal</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Alle</numIndex>
<numIndex index="1">0</numIndex>
<label>Alle</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -165,12 +165,12 @@
<renderType>selectSingle</renderType>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Spalten</numIndex>
<numIndex index="1">cols</numIndex>
<label>Spalten</label>
<value>cols</value>
</numIndex>
<numIndex index="1" type="array">
<numIndex index="0">Reihen</numIndex>
<numIndex index="1">rows</numIndex>
<label>Reihen</label>
<value>rows</value>
</numIndex>
</items>
</config>
@@ -185,24 +185,24 @@
<renderType>selectCheckBox</renderType>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Ferienwohnung</numIndex>
<numIndex index="1">1</numIndex>
<label>Ferienwohnung</label>
<value>1</value>
</numIndex>
<numIndex index="1" type="array">
<numIndex index="0">Sportclub</numIndex>
<numIndex index="1">2</numIndex>
<label>Sportclub</label>
<value>2</value>
</numIndex>
<numIndex index="2" type="array">
<numIndex index="0">Apartment</numIndex>
<numIndex index="1">3</numIndex>
<label>Apartment</label>
<value>3</value>
</numIndex>
<numIndex index="3" type="array">
<numIndex index="0">G&#xE4;stehaus</numIndex>
<numIndex index="1">4</numIndex>
<label>G&#xE4;stehaus</label>
<value>4</value>
</numIndex>
<numIndex index="4" type="array">
<numIndex index="0">Pension</numIndex>
<numIndex index="1">5</numIndex>
<label>Pension</label>
<value>5</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -228,8 +228,8 @@
<foreign_table_where>ORDER BY title</foreign_table_where>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Default</numIndex>
<numIndex index="1">0</numIndex>
<label>Default</label>
<value>0</value>
</numIndex>
</items>
<minitems>0</minitems>
@@ -29,12 +29,12 @@
<renderType>selectSingle</renderType>
<items type="array">
<numIndex index="0" type="array">
<numIndex index="0">Winter</numIndex>
<numIndex index="1">w</numIndex>
<label>Winter</label>
<value>w</value>
</numIndex>
<numIndex index="1" type="array">
<numIndex index="0">Sommer</numIndex>
<numIndex index="1">s</numIndex>
<label>Sommer</label>
<value>s</value>
</numIndex>
</items>
<minitems>1</minitems>
@@ -33,7 +33,6 @@
<config>
<type>select</type>
<renderType>selectMultipleSideBySide</renderType>
<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
<foreign_table>sys_category</foreign_table>
<minitems>0</minitems>
<maxitems>99</maxitems>
@@ -127,6 +127,12 @@ call_user_func(function () {
'FAQ'
);
ExtensionUtility::registerPlugin(
'EpProducts',
'newsletter_subscription',
'Newsletter-/RA-Registrierung'
);
ExtensionUtility::registerPlugin(
'EpProducts',
'maps',
+12
View File
@@ -7,6 +7,7 @@ use EP\EpProducts\Controller\CountryController;
use EP\EpProducts\Controller\FaqController;
use EP\EpProducts\Controller\HotelController;
use EP\EpProducts\Controller\MapController;
use EP\EpProducts\Controller\NewsletterController;
use EP\EpProducts\Controller\PricetableController;
use EP\EpProducts\Controller\RegionController;
use EP\EpProducts\Controller\TeamController;
@@ -174,6 +175,17 @@ $boot = function () {
]
);
ExtensionUtility::configurePlugin(
'EpProducts',
'newsletter_subscription',
[
NewsletterController::class => 'subscriptionForm,subscriptionFormSubmit',
],
[
NewsletterController::class => 'subscriptionForm,subscriptionFormSubmit',
]
);
ExtensionUtility::configurePlugin(
'EpProducts',
'maps',
@@ -0,0 +1,18 @@
<?php
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue;
use TYPO3\CMS\Core\Type\Map;
// The frontend references favicons and images on the CDN, and backend previews (yoast's snippet
// preview) show them as they are.
return Map::fromEntries([
Scope::backend(),
new MutationCollection(
new Mutation(MutationMode::Extend, Directive::ImgSrc, new UriValue('https://cdn.ep-reisen.de')),
),
]);
@@ -1,7 +1,6 @@
# Retired 2026-10-24 (newsletter form and subscription, reseller export, group price calculator, which
# Retired 2026-10-24 (newsletter form, reseller export, group price calculator, which
# MyE&P replaced): the elements stay in the database and render nothing; the old code is in a46918d
# should they come back
tt_content.nlform = TEXT
tt_content.list.20.epproducts_newsletter_subscription = TEXT
tt_content.list.20.epproducts_reseller = TEXT
tt_content.list.20.epproducts_hotel_groups_price = TEXT
@@ -4,8 +4,8 @@
plugin.tx_form.view {
templateRootPaths.10 = EXT:ep_theme/Resources/Private/Templates/Form/
# Spinner.html for Render.html
partialRootPaths.10 = EXT:ep_theme/Resources/Private/Partials/Form/
# Form/Island.html for Render.html
partialRootPaths.10 = EXT:ep_theme/Resources/Private/Partials/
}
plugin.tx_form.settings.yamlConfigurations {
@@ -4,6 +4,7 @@ page {
viewport = width=device-width, initial-scale=1
X-UA-Compatible = ie=edge
format-detection = telephone=no
google-site-verification = {$plugin.tx_eptheme.settings.googleSiteVerification}
}
bodyTagCObject = COA
@@ -40,32 +41,17 @@ page {
headerData.100 = TEXT
headerData.100.value = <link rel="preconnect" href="https://cdn.ep-reisen.de">
# The set realfavicongenerator.net generates (2024): what current browsers use, nothing for
# outdated ones. faviconPath is the folder in fileadmin it was uploaded to.
headerData.333 = TEXT
headerData.333.value (
<link rel="shortcut icon" type="image/x-icon" href="/{path:EXT:ep_theme/Resources/Public/images/favicon.ico}">
<link rel="icon" type="image/x-icon" href="/{path:EXT:ep_theme/Resources/Public/images/favicon.ico}">
<link rel="icon" type="image/gif" href="/{path:EXT:ep_theme/Resources/Public/images/favicon.gif}">
<link rel="icon" type="image/png" href="/{path:EXT:ep_theme/Resources/Public/images/favicon.png}">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon.png}">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-57x57.png}" sizes="57x57">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-60x60.png}" sizes="60x60">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-72x72.png}" sizes="72x72">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-76x76.png}" sizes="76x76">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-114x114.png}" sizes="114x114">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-120x120.png}" sizes="120x120">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-128x128.png}" sizes="128x128">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-144x144.png}" sizes="144x144">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-152x152.png}" sizes="152x152">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-180x180.png}" sizes="180x180">
<link rel="apple-touch-icon" href="/{path:EXT:ep_theme/Resources/Public/images/apple-touch-icon-precomposed.png}">
<link rel="icon" type="image/png" href="/{path:EXT:ep_theme/Resources/Public/images/favicon-16x16.png}" sizes="16x16">
<link rel="icon" type="image/png" href="/{path:EXT:ep_theme/Resources/Public/images/favicon-32x32.png}" sizes="32x32">
<link rel="icon" type="image/png" href="/{path:EXT:ep_theme/Resources/Public/images/favicon-96x96.png}" sizes="96x96">
<link rel="icon" type="image/png" href="/{path:EXT:ep_theme/Resources/Public/images/favicon-160x160.png}" sizes="160x160">
<link rel="icon" type="image/png" href="/{path:EXT:ep_theme/Resources/Public/images/favicon-192x192.png}" sizes="192x192">
<link rel="icon" type="image/png" href="/{path:EXT:ep_theme/Resources/Public/images/favicon-196x196.png}" sizes="196x196">
<link rel="icon" type="image/png" href="/{$plugin.tx_eptheme.settings.faviconPath}/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/{$plugin.tx_eptheme.settings.faviconPath}/favicon.svg" />
<link rel="shortcut icon" href="/{$plugin.tx_eptheme.settings.faviconPath}/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/{$plugin.tx_eptheme.settings.faviconPath}/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="{$plugin.tx_eptheme.settings.faviconAppTitle}" />
<link rel="manifest" href="/{$plugin.tx_eptheme.settings.faviconPath}/site.webmanifest" />
)
headerData.333.insertData = 1
# disableCompression keeps ResourceCompressor's hands off the Encore entries: Webpack
# already minifies them, and copying them to typo3temp/assets/compressed only replaces the
@@ -129,6 +129,10 @@ plugin.tx_eptheme {
defaultBodyclass = subpage
# cat=eptheme/500/120; type=string; label=Logo image
logoImage = EXT:ep_theme/Resources/Public/images/logo_wide.svg
# cat=eptheme/500/125; type=string; label=Favicon folder (realfavicongenerator set, without leading/trailing slash)
faviconPath = fileadmin/user_upload/favicon/ep
# cat=eptheme/500/126; type=string; label=Home screen title (apple-mobile-web-app-title)
faviconAppTitle = E&P Reisen und Events
# cat=eptheme/500/130; type=string; label=Force active topnav uid
forceActiveTopnavUid =
# cat=eptheme/500/140; type=string; label=Booking URL template code
@@ -166,6 +170,8 @@ plugin.tx_eptheme {
googleMapsApiKey = AIzaSyDDOforD1O_j4xXy2nJybC2yQqbUAw9ClY
# cat=eptheme/300/130; type=string; label=Canonical base URL/Domain
canonicalBaseUrl = https://www.ep-reisen.de
# cat=eptheme/300/140; type=string; label=Google site verification
googleSiteVerification =
# cat=eptheme.events/700/100; type=string; label=Event date day from
eventDateDayFrom =
@@ -76,9 +76,6 @@ plugin.tx_eptheme {
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}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

@@ -2,7 +2,6 @@
{
"loaderUrl": "<f:format.raw>{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif', absolute: 1)}</f:format.raw>",
"iconSpriteUrl": "<f:format.raw>{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg', absolute: 1)}</f:format.raw>",
"myEpApiBaseUrl": "<f:format.raw>{settings.myEpApiEndpointUrl}</f:format.raw>",
"paCode": <f:if condition='{context.paCode}'><f:then>"{context.paCode}"</f:then><f:else>null</f:else></f:if>,
"externalDomains": <f:format.raw>{settings.domainsToTrack -> v:iterator.explode() -> f:format.json() -> f:format.raw()}</f:format.raw>
}
@@ -0,0 +1,19 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<div class="form-island"{f:if(condition: id, then: ' id="{id}"')}>
{content -> f:format.raw()}
<div class="form-island__loading" aria-hidden="true">
<f:render partial="Form/Spinner" arguments="{class: 'form-island__spinner'}" />
</div>
<style>
.form-island { position: relative; }
.form-island__loading { position: absolute; inset: 0; z-index: 10; display: flex; align-items: center; justify-content: center; background: rgb(255 255 255 / .7); visibility: hidden; opacity: 0; transition: opacity .15s, visibility .15s; }
.form-island.htmx-request .form-island__loading { visibility: visible; opacity: 1; }
.form-island__spinner { width: 6rem; height: 6rem; color: rgb(24 82 123); }
.sbw .form-island__spinner { color: rgb(233 28 38); }
.snz .form-island__spinner { color: rgb(249 195 61); }
.suz .form-island__spinner { color: rgb(130 195 198); }
.uch .form-island__spinner { color: rgb(25 163 220); }
.ser .form-island__spinner { color: rgb(222 44 27); }
</style>
</div>
</html>
@@ -1,31 +1,10 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers" data-namespace-typo3-fluid="true">
<f:comment>
Every form sits in a .form-island, on pages TYPO3 13 renders as well as in the form page type
(/path/form) that the static pages load: the form posts there, and htmx swaps the island as a
whole (see Form.html). While a request runs, htmx sets htmx-request on it and the overlay
covers the form. Styled here and not in the theme build: the static pages around the island
load the CSS of the TYPO3 10 export. The spinner takes the button colour of each theme.
</f:comment>
<div class="form-island">
<f:render partial="Form/Island" contentAs="content">
<f:flashMessages />
<f:if condition="{formConfiguration}">
<div class="py-8">
<formvh:render overrideConfiguration="{formConfiguration}" />
</div>
</f:if>
<div class="form-island__loading" aria-hidden="true">
<f:render partial="Spinner" arguments="{class: 'form-island__spinner'}" />
</div>
<style>
.form-island { position: relative; }
.form-island__loading { position: absolute; inset: 0; z-index: 10; display: flex; align-items: center; justify-content: center; background: rgb(255 255 255 / .7); visibility: hidden; opacity: 0; transition: opacity .15s, visibility .15s; }
.form-island.htmx-request .form-island__loading { visibility: visible; opacity: 1; }
.form-island__spinner { width: 6rem; height: 6rem; color: rgb(24 82 123); }
.sbw .form-island__spinner { color: rgb(233 28 38); }
.snz .form-island__spinner { color: rgb(249 195 61); }
.suz .form-island__spinner { color: rgb(130 195 198); }
.uch .form-island__spinner { color: rgb(25 163 220); }
.ser .form-island__spinner { color: rgb(222 44 27); }
</style>
</div>
</f:render>
</html>
@@ -0,0 +1,112 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:layout name="Default"/>
<f:section name="Main">
<a id="newsletter-form" class="anchor"></a>
<f:render partial="Form/Island" arguments="{id: 'newsletter-island'}" contentAs="content">
<div class="container">
<f:form method="post" action="subscriptionFormSubmit" name="subscriptionRequest" section="newsletter-form" object="{subscriptionRequest}"
hx-boost="true"
hx-target="#newsletter-island"
hx-select="#newsletter-island"
hx-swap="outerHTML"
hx-push-url="false"
hx-indicator="#newsletter-island">
<div class="grid lg:grid-cols-2 lg:gap-x-8 items-start">
<div>
<f:render section="Field" arguments="{property: 'firstName', label: 'Vorname'}"/>
<f:render section="Field" arguments="{property: 'lastName', label: 'Nachname'}"/>
<f:render section="Field" arguments="{property: 'email', label: 'E-Mail'}"/>
<div class="mb-4">
<f:form.validationResults for="subscriptionRequest.consent">
<label class="inline-flex items-center">
<f:form.checkbox property="consent" value="1" class="form-checkbox{f:if(condition: validationResults.errors.0, then: ' form-checkbox--has-error')}"/>
<span class="block ml-2">Ich akzeptiere die <f:link.typolink parameter="{settings.dataProtectionPageUid}" class="underline" target="_blank">Datenschutzbestimmungen</f:link.typolink>*</span>
</label>
<f:if condition="{validationResults.flattenedErrors}">
<div class="text-red-500 mt-2" role="alert">
<f:for each="{validationResults.errors}" as="error">
{f:translate(key: 'tx_eptheme.message.newsletter.consent.{error.code}', extensionName: 'ep_theme')}
<br />
</f:for>
</div>
</f:if>
</f:form.validationResults>
</div>
<div class="hidden lg:block">
<button type="submit" class="button bg-button">
Anmelden
</button>
</div>
</div>
<div>
<div>
<f:form.validationResults for="subscriptionRequest.listIds">
<f:for each="{lists}" as="list" iteration="iteration">
<f:comment><!-- show different headline for first newsletter in list --></f:comment>
<f:if condition="{iteration.isFirst}">
<f:then>
<h3>
News &amp; Angebote per Mail
</h3>
</f:then>
<f:else>
<f:comment><!-- show headline only once --></f:comment>
<f:if condition="{alertHeadline}">
<f:else>
<f:variable name="alertHeadline" value="1"/>
<h3 class="pt-4">
Benachrichtige mich bei Verfügbarkeit
</h3>
</f:else>
</f:if>
</f:else>
</f:if>
<label for="list-{list.id}" class="flex items-center">
<f:form.checkbox property="listIds" multiple="1" value="{list.id}" class="form-checkbox"/>
<span class="block ml-2">{list.label}</span>
</label>
</f:for>
<f:if condition="{validationResults.flattenedErrors}">
<div class="text-red-500 mt-2" role="alert">
<f:for each="{validationResults.errors}" as="error">
{f:translate(key: 'tx_eptheme.message.newsletter.listIds.{error.code}', extensionName: 'ep_theme')}
<br />
</f:for>
</div>
</f:if>
</f:form.validationResults>
</div>
</div>
<div class="lg:hidden mt-4">
<button type="submit" class="button bg-button">
Anmelden
</button>
</div>
</div>
</f:form>
</div>
</f:render>
</f:section>
<f:section name="Field">
<f:form.validationResults for="subscriptionRequest.{property}">
<f:variable name="hasErrors" value="{validationResults.errors.0}"/>
<label class="block mb-4">
<span class="{f:if(condition: hasErrors, then: 'text-red-500', else: 'text-zinc-700')}">{label}*</span>
<f:form.textfield property="{property}" class="form-field {f:if(condition: hasErrors, then: 'form-field--has-error')}"/>
<f:if condition="{validationResults.flattenedErrors}">
<div class="text-red-500 mt-2" role="alert">
<f:for each="{validationResults.errors}" as="error">
{f:translate(key: 'tx_eptheme.message.contactForm.{property}.{error.code}', extensionName: 'ep_theme')}
<br />
</f:for>
</div>
</f:if>
</label>
</f:form.validationResults>
</f:section>
</html>
@@ -0,0 +1,41 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:layout name="Default"/>
<f:section name="Main">
<a id="newsletter-form" class="anchor"></a>
<f:render partial="Form/Island" arguments="{id: 'newsletter-island'}" contentAs="content">
<div class="container">
<f:if condition="{result.success}">
<f:then>
<div class="bg-ep-primary-light text-white p-4">
<h2>
Vielen Dank für dein Interesse
</h2>
<p>
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.
</p>
<p class="text-sm">
Sieh am besten auch im Spam-Ordner nach.
</p>
</div>
</f:then>
<f:else>
<div class="bg-red-600 text-white p-4" role="alert">
<h2>
Das hat leider nicht geklappt
</h2>
<p>
Deine Anmeldung konnte gerade nicht gespeichert werden. Bitte versuche es in ein paar Minuten
<f:link.page section="newsletter-form" absolute="1" class="underline">noch einmal</f:link.page>.
</p>
</div>
</f:else>
</f:if>
</div>
</f:render>
</f:section>
</html>
File diff suppressed because it is too large. Load diff
+43
View File
@@ -0,0 +1,43 @@
/*! Package version @n8n/[email protected] */
:root{--chat--color--primary:#e74266;--chat--color--primary-shade-50:#db4061;--chat--color--primary--shade-100:#cf3c5c;--chat--color--secondary:#20b69e;--chat--color-secondary-shade-50:#1ca08a;--chat--color-white:#fff;--chat--color-light:#f2f4f8;--chat--color-light-shade-50:#e6e9f1;--chat--color-light-shade-100:#c2c5cc;--chat--color-medium:#d2d4d9;--chat--color-dark:#101330;--chat--color-disabled:#d2d4d9;--chat--color-typing:#404040;--chat--spacing:1rem;--chat--border-radius:.25rem;--chat--transition-duration:.15s;--chat--font-family:( -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif );--chat--window--width:400px;--chat--window--height:600px;--chat--window--bottom:var(--chat--spacing);--chat--window--right:var(--chat--spacing);--chat--window--z-index:9999;--chat--window--border:1px solid var(--chat--color-light-shade-50);--chat--window--border-radius:var(--chat--border-radius);--chat--window--margin-bottom:var(--chat--spacing);--chat--header-height:auto;--chat--header--padding:var(--chat--spacing);--chat--header--background:var(--chat--color-dark);--chat--header--color:var(--chat--color-light);--chat--header--border-top:none;--chat--header--border-bottom:none;--chat--header--border-left:none;--chat--header--border-right:none;--chat--heading--font-size:2em;--chat--subtitle--font-size:inherit;--chat--subtitle--line-height:1.8;--chat--message--font-size:1rem;--chat--message--padding:var(--chat--spacing);--chat--message--border-radius:var(--chat--border-radius);--chat--message-line-height:1.5;--chat--message--margin-bottom:calc(var(--chat--spacing)*1);--chat--message--bot--background:var(--chat--color-white);--chat--message--bot--color:var(--chat--color-dark);--chat--message--bot--border:none;--chat--message--user--background:var(--chat--color--secondary);--chat--message--user--color:var(--chat--color-white);--chat--message--user--border:none;--chat--message--pre--background:#0000000d;--chat--messages-list--padding:var(--chat--spacing);--chat--toggle--size:64px;--chat--toggle--width:var(--chat--toggle--size);--chat--toggle--height:var(--chat--toggle--size);--chat--toggle--border-radius:50%;--chat--toggle--background:var(--chat--color--primary);--chat--toggle--hover--background:var(--chat--color--primary-shade-50);--chat--toggle--active--background:var(--chat--color--primary--shade-100);--chat--toggle--color:var(--chat--color-white);--chat--textarea--height:50px;--chat--textarea--max-height:30rem;--chat--input--font-size:inherit;--chat--input--border:0;--chat--input--border-radius:0;--chat--input--padding:.8rem;--chat--input--background:var(--chat--color-white);--chat--input--text-color:initial;--chat--input--line-height:1.5;--chat--input--placeholder--font-size:var(--chat--input--font-size);--chat--input--border-active:0;--chat--input--left--panel--width:2rem;--chat--button--color:var(--chat--color-light);--chat--button--background:var(--chat--color--primary);--chat--button--padding:calc(var(--chat--spacing)*1/2)var(--chat--spacing);--chat--button--border-radius:var(--chat--border-radius);--chat--button--hover--color:var(--chat--color-light);--chat--button--hover--background:var(--chat--color--primary-shade-50);--chat--close--button--color-hover:var(--chat--color--primary);--chat--input--send--button--background:var(--chat--color-white);--chat--input--send--button--color:var(--chat--color--secondary);--chat--input--send--button--background-hover:var(--chat--color--primary-shade-50);--chat--input--send--button--color-hover:var(--chat--color-secondary-shade-50);--chat--input--file--button--background:var(--chat--color-white);--chat--input--file--button--color:var(--chat--color--secondary);--chat--input--file--button--background-hover:var(--chat--input--file--button--background);--chat--input--file--button--color-hover:var(--chat--color-secondary-shade-50);--chat--files-spacing:.25rem;--chat--body--background:var(--chat--color-light);--chat--footer--background:var(--chat--color-light);--chat--footer--color:var(--chat--color-dark)}pre code.hljs{padding:1em;display:block;overflow-x:auto}code.hljs{padding:3px 5px}.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#005cc5}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-comment,.hljs-code,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-wLine truncated
/*$vite$:1*/
:root {
/* EP Reisen Brand Colors */
--chat--color--primary: #18527b;
--chat--color--primary-shade-50: #1a8dc4;
--chat--color--primary--shade-100: #177db0;
--chat--color--secondary: #be167d;
--chat--color-secondary-shade-50: #ab1471;
/* Layout & Spacing */
--chat--border-radius: 12px;
--chat--font-size: 0.9rem;
--chat--spacing: 1rem;
--chat--window--width: 420px;
--chat--window--height: 650px;
/* Header Styling */
--chat--header--background: #1d9dd9;
--chat--header--color: #ffffff;
--chat--heading--font-size: 1.5em;
--chat--header--padding: 1.2rem;
/* Message Bubbles */
--chat--message--font-size: 0.95rem;
--chat--message--border-radius: 12px;
--chat--message--bot--background: #f5f5f5;
--chat--message--bot--color: #333333;
--chat--message--user--background: #be167d;
--chat--message--user--color: #ffffff;
/* Toggle Button */
--chat--toggle--background: #1d9dd9;
--chat--toggle--hover--background: #1a8dc4;
--chat--toggle--active--background: #177db0;
--chat--toggle--color: #ffffff;
--chat--toggle--size: 60px;
/* Input Field */
--chat--textarea--height: 55px;
}
+1
View File
@@ -0,0 +1 @@
{"name":"E&P Skireisen","short_name":"E&P Skireisen","start_url":"/","display":"standalone","gcm_sender_id":"890396972010","gcm_user_visible_only":true}
+1
View File
@@ -0,0 +1 @@
importScripts('https://static.cleverpush.com/channel/worker/MrDvZ9L2bngnLfnq9.js' + self.location.search);