Compare commits

...
87 Commits
Author SHA1 Message Date
frommeandClaude Opus 5.5 81a8013e8a feat: ep_products plugins are content element types of their own
Preparation for TYPO3 14, which drops list_type. The plugins are
registered as CTypes named like their former list_type values
(epproducts_product_detail etc.); the form keeps the fields of the
"list" type, with the plugin options in place of the plugin selector. As content
element types they show the E&P logo of the extension manager
(icon ep-products-plugin) instead of the generic plugin icon.

The upgrade wizard epProductsPluginListTypeToCType moves the elements
and the tt_content:list_type permissions of backend groups. Until it has
run, list_type elements render through the new types (alias block in
ext_localconf.php), so the deploy alone changes nothing. Retired plugins
stay "list" elements.

The content-only page type, the content manifest and the two container
columns limited to "list" now treat the epproducts_* types as plugins.

Crawl of 1,912 URLs identical for old code and new code before and after
the wizard; rehearsed again on the prod dump of 2026-11-01 (1,924 URLs,
API snapshot identical). Rollout and rollback SQL: .ddev/plans/PLUGINS-CTYPE-ROLLOUT.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-01 14:31:35 +01:00
fromme 518f29b160 chore: install typo3 console extension 2026-10-31 10:39:13 +01:00
frommeandClaude Opus 5.5 5b37b37a78 fix: event price table no longer shows "0" for missing room types
Every hotel row renders a column for each configured pax count (1–10),
but most hotels only offer some of them. For a missing room type the
"not available" branch ran and ep:price() turned the null price into a
struck-through "0". The nested f:if there repeated the outer condition
and so never filtered anything.

Cells now only show a struck-through price when the room has one, and
stay empty otherwise. The bug already existed on TYPO3 10.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 08:11:37 +01:00
frommeandClaude Opus 5.5 ab2b840dc5 feat: search result teasers list all bookable nights options
Search teasers showed only the nights of the cheapest offer, while
teaser groups list every option. They now list all nights options next
to the cheapest "ab" price, like teaser groups and the watchlist do.

All search filters are applied in SQL before the rows are aggregated,
so the list only holds options matching the active filters (e.g. the
duration filter).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 16:05:45 +01:00
frommeandClaude Opus 5.5 4cefbce87e fix: /api/product no longer returns another product for some codes
The product lookup also matched hotel aliases (matchcodes), joined on
the product uid. Matchcodes only exist for hotels, so a hotel alias
equal to the requested code matched whatever product had that hotel's
uid. With no ordering, the database picked one of the rows: locally
DPWGRU3 returned SnowZone and KHHMP returned Hotel Terrace instead of
Roncalli Düren and Gasthof Heidelberg. The other 313 product codes
return the same response as before.

Products are looked up by code only. Some codes exist on two products
(e.g. MVKPGW); ordering by uid makes the result deterministic.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 15:46:55 +01:00
frommeandClaude Opus 5.5 2bd663a9bf docs: CMS API and calendars
docs/cms-api.md describes the endpoints TYPO3 serves to MyE&P and the
chatbot (/api/hotel, /api/product, /api/calendar-hotels,
/api/content/*): key, parameters, responses, errors. It is the other
side of MyE&P's API consumer guide.

docs/calendars.md covers the price calendar, the Belegungskalender and
the iCal feed: which product gets which, the MyE&P endpoints behind
them, caching, troubleshooting, and the rule that a product's calendar
hotel must carry the BusPro code, not a group house's CMS code (which
broke three example trips).

The README replaces the TYPO3 base distribution boilerplate and links
both. The plugin renaming SQL in ep_products' README is obsolete since
the migration. The cmsApiKey description names /api/calendar-hotels.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 15:37:36 +01:00
frommeandClaude Opus 5.5 bf9afd143d feat: API lists the calendar hotels for the contingent sync of MyE&P
GET /api/calendar-hotels returns the calendar hotel of every visible
product, with the products using it and their season, behind the same
API key as /api/hotel and /api/product.

MyE&P only syncs contingent snapshots for its accommodations, so hotels
that are only offered in winter (e.g. DKI, DWW, ASCGR) have no
availability for the iCal feed and the availability calendar. With this
list MyE&P can sync every hotel code TYPO3 actually uses, without a
second list to maintain. The products and seasons are for a status
overview of which hotel supports iCal, availability and prices.

Hidden hotels are left out, as on the website (e.g. ASB).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 14:45:44 +01:00
frommeandClaude Opus 5.5 5d41660a9a feat: availability calendar in the product detail sidebar is back
TYPO3 10 showed a "Belegungskalender" in the sidebar of every product
with a calendar hotel. It was dropped when the price calendar arrived,
and the price calendar is limited to summer products, so winter
products showed no availability at all.

The sidebar calendar is shown for products with a calendar hotel that
are not summer products. AjaxCalendar::availability reads the
availability from MyE&P (contingents/availability, as the iCal feed
does) instead of bpn-connect, and renders all months in one cacheable
response; the month select only switches between them. Months with
prices in the price config (summer, covered by the price calendar) are
left out. If the price config is unavailable, all months are shown
without caching.

The plain availability-* classes are safelisted in Tailwind: they were
only kept on TYPO3 10 because the ContingentViewHelper named them, and
the class names are now built in the controller.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 14:03:59 +01:00
fromme c63cbdd3a6 feat: redirects for legacy domains 2026-10-29 12:19:59 +01:00
fromme e9bda426f2 chore: update project dependencies 2026-10-29 11:28:27 +01:00
frommeandClaude Opus 5.5 63e3ed4a78 feat: the iCal contingent feed for the booking portals is back
/contingents/<hotel>.ical (page type 1710) serves the same events as on
TYPO3 10: every run of BLOCKED or ON_REQUEST days with one status is one
"Unavailable" event. External booking portals import it.

The availability comes from MyE&P contingents/availability, the
bpn-connect snapshot without the price rule of contingents/calendar
(days without a price stay free here, as they did on TYPO3 10).
TYPO3 10 read bpn-connect directly. Compared for SVS, DPW and DGS: the
events are identical; the header differs only by the iCalcreator
version (2.41 instead of 2.39) and its TZUNTIL, both RFC-conform.

When MyE&P cannot answer, the feed returns 503 with Retry-After instead
of an empty calendar, so the portals keep their last import rather than
seeing every day as free. The response is sent past the page cache,
which would drop Content-Disposition, and staticfilecache is off for the
page type.

Requires the MyE&P endpoint on my.ep-reisen.de before deploying; until
then every feed answers 503.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 11:26:14 +01:00
frommeandClaude Opus 5.5 9aabff6b5b feat: dashboard widget shows the import status
Replaces the TYPO3 10 system information entry "Letzer Produktdaten-Import":
for travel data, snow reports and groups.swiss the widget shows the data
state, the last and next scheduler run and the last error, plus MyE&P's
last export. Tasks are found by their command, not by uid. A task whose
next run is more than an hour overdue means scheduler:run is no longer
called.

New dashboards get an "Importe" tab; it can also be added via the wizard.
Editors need the Dashboard module and the widget enabled in the office
group (backend, after the deploy).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-28 17:34:50 +01:00
frommeandClaude Opus 5.5 863dd9de1a feat: site settings are edited in the backend, per environment
The constants of the sites become editable in Site Management >
Settings (admins only in TYPO3 13.4). settings.definitions.yaml of the
set ep/theme defines all 116 constants plus googleTagManagerId, with the
labels and categories of their constant editor annotations and today's
constants.typoscript values as defaults. Every setting is a string,
yes/no ones with a select of exactly '0' and '1': a bool setting would
hand '' instead of '0' to TypoScript.

config/sites/*/settings.yaml become shared files on the server and leave
git, like settings.php: production is leading, so backend edits survive
deploys, and each environment keeps its own values. Nine settings nobody
reads (twitterUrl, vimeoUrl, cookiefirstKey, ...) were dropped locally;
on production they stay in the seeded files until removed there.

deploy:check:site-settings aborts before anything else touches the
server if one of the shared files is missing or empty: deploy:shared
would create it empty, silently resetting that site to the defaults.

Rollout, after the site set is live: seed the shared files from the
live release once, then deploy:
  cd <deploy_path> && for f in current/config/sites/*/settings.yaml; do
    mkdir -p "shared/${f#current/}" && rmdir "shared/${f#current/}" &&
    cp -p "$f" "shared/${f#current/}"; done

Verified locally: the resolved setup of 43 pages is unchanged and only
the nine unused constants are gone (switched state); the root template
state is unchanged; all 1912 frontend URLs are identical.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 12:05:23 +01:00
frommeandClaude Opus 5.5 26a7d5f78a chore: extension icons from the E&P logo, ep_theme is stable
The icon is the E&P signet on a square canvas. ep_theme's
Resources/Public is build output, so its icon is copied there by
the asset build from Resources/Private/Assets/icons.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 11:46:36 +01:00
frommeandClaude Opus 5.5 8f667e92ee chore: EXT:felogin removed
Not in use: no login form in the content, and no group grants it.
Its TypoScript and the felogin_login content type are gone with it;
the TypoScript of all other keys and the frontend output are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 11:46:36 +01:00
frommeandClaude Opus 5.5 0ac58c1033 feat: sites get their TypoScript from the site set ep/theme
The set includes the same ep_theme TypoScript the root templates
included statically, and config/sites/*/settings.yaml hold the
constants of each site's root template, generated with TYPO3's own
TypoScript parser and kept under their old names, so no TypoScript
changes. They are plain values without definitions, so nothing is
type-converted.

Rolling out takes a second step: the root templates, with their clear
flags, still replace everything the set provides, so this deploy alone
changes nothing. .ddev/plans/sql/2026-10-27-site-sets.sql then hides
them and moves their setup (date picker presets, partner IDs, ...) into
extension templates editors keep maintaining. Extension templates
further down the tree keep applying on top.

Verified locally on a prod copy:
- the resolved constants and setup of 43 pages (every site root, every
  page with an extension template below it, every page layout) are
  identical before the deploy, between deploy and SQL, after the SQL
  and after its rollback
- a crawl of all 1912 frontend URLs is identical before and after

Rollback: always run 2026-10-27-site-sets-rollback.sql BEFORE rolling
back this code, or the sites have no TypoScript at all in between.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 11:46:26 +01:00
frommeandClaude Opus 5.5 903292d84c feat: products can have their own text in the subsite popup
The popup that announces the switch to another site (e.g. from a
teaser on ep-reisen.de to semesterende-skireisen.de) showed the text of
the product's concept for every product. A product now has an optional
popup text (new "Popup" tab) that replaces the concept's; empty, the
concept's text stays. Title, image and button remain the concept's, and
whether a popup appears at all is still up to the concept.

Teasers pass the product to the popup request. Pages cached before the
deploy request it without one and get the concept's text, as before.

The new column tx_epproducts_domain_model_product.popup_text is the only
pending schema change; the deploy's extension:setup adds it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 11:41:18 +01:00
frommeandClaude Opus 5.5 170e14dae5 fix: product pages no longer serve one visitor's back link to everyone
The "back to search results" link took ?ref= as it came and was cached
with the page, which ignores ref: the first visitor of a product page
set the link for every later one, and a javascript: URL in ref put
script into the page for all of them (verified locally).

The page now always renders the link hidden and without a target; the
backlink Stimulus controller fills in the visitor's own ?ref= and shows
it, only for URLs of the same origin. The label stays "zurück zum
Suchergebnis": refs only come from the search, and the old check
(strpos() !== -1) always picked that label anyway.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 11:28:11 +01:00
frommeandClaude Opus 5.5 b428ce9086 feat: ddev anonymize-db removes personal data from production dumps
Runs automatically after every `ddev import-db`. Drops all zzz_deleted_
tables (renamed in production but still holding their data, e.g. the
retired address check and form answers), replaces names and emails of
backend and frontend users, sets every password to the local dev
password "password" and clears reset tokens and MFA secrets. Sessions,
record locks, CSP reports and logs are emptied (IP addresses, user
agents). Usernames stay, so everybody logs in locally as their own user.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 10:13:02 +01:00
frommeandClaude Opus 5.5 9265e0d1c1 fix: CKEditor 5 list item ids no longer end up in the content
CKEditor 5 saves a data-list-item-id attribute on every list item, which
it only needs inside the editor; it bloated the frontend HTML and the RTE
content the API and the content feed hand to MyE&P. Both RTE presets now
import ep_theme's Processing.yaml, which removes the attribute on the way
into the database (exitHTMLparser_db); everything else passes unchanged.

Content saved since the go-live is cleaned once by hand after the
deploy (.ddev/plans/sql/2026-10-26-rte-list-item-ids.sql).

Checked locally as an editor: an RTE field with list item ids is stored
without them, other attributes (class on li, data-* on links) stay;
without the change the ids are stored.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 13:05:46 +01:00
frommeandClaude Opus 5.5 c61dddfb1d chore: travel dates are no longer translatable
Dates are written by the import only and are never translated (all rows
are in the default language). Like the matchcodes, the table loses
languageField, transOrigPointerField, transOrigDiffSourceField, its
language columns and the Language tab in TCA; the database columns stay
until the schema clean-up.

Checked locally: frontend crawl (~1,900 URLs incl. the English pages) and
the backchannel APIs render the same as before, the editor check creates
records in all other tables.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 13:00:17 +01:00
frommeandClaude Opus 5.5 7d3b6faf6c fix: editors can create facts and lineup items again
New facts failed with "Field 'keywords' doesn't have a default value":
the column is TEXT NOT NULL without a database default and not part of
the form. It now gets a TCA default, like the passthrough text columns of
dates that are not in the form either.

Lineup items are inline children of the lineup content element on
standard pages. TYPO3 10 allowed the table there with
allowTableOnStandardPages(); the migration to
ctrl.security.ignorePageTypeRestriction only covered the slides, so
adding a lineup item failed with "Attempt to insert record on pages:...
where table is not allowed".

Checked locally as an editor: a new record of every table of ep_products
and ep_theme goes through FormEngine and DataHandler (dates excepted:
hidden, written by the import only).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 12:55:06 +01:00
frommeandClaude Opus 5.5 99cd3c1898 fix: editors can create records in the folders outside the sites again
Editors got "recordEditAccessInternals() check failed [ERROR: The
"languageField" field named "sys_language_uid" was not found in testing
record!]" for every new record of our tables, e.g. a hotel's additional
code or a new hotel. The record folders lie on the root level, outside
every site, and no form submitted the language field. TYPO3 10 then took
language 0 from sys_language; since TYPO3 11, DataHandler only takes the
default language from the record's site and sets none without a site, so
the access check for non-admins fails.

As stock TYPO3 expects it, the forms now submit the language: a Language
tab with sys_language_uid (default 0) and l10n_parent in the 27
language-aware tables of ep_products and ep_theme. Matchcodes (a hotel's
additional codes) are no longer translatable at all: none was ever
translated, and the feed, the API and the import read them without
language handling.

LabelService no longer reads from a missing record when the form of a
new record asks for a label.

Checked locally as an editor: FormEngine preselects language 0, and new
hotels, products, FAQs and additional codes are saved.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 12:31:55 +01:00
frommeandClaude Opus 5.5 ecd5ea66c5 refactor: type declarations via Rector
rector.php gets the typeDeclarations set: return types (void, array,
unions) where the code proves them, typed closures and arrow functions,
typed properties from constructors and assignments in the DTOs.

Skipped: rules that infer scalar or array parameter types from call
sites or docblocks, and the removal of casts the docblocks call
redundant. Values from the database, form input and the import feeds
arrive as strings and would throw instead of being converted (e.g. the
(int) cast of the newsletter list ids stays).

Checked locally: cache warmup, frontend crawl (~1,900 URLs), the
backchannel APIs and the newsletter form validation behave as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 11:21:12 +01:00
frommeandClaude Opus 5.5 4fe06267c9 refactor: code quality via Rector, strict types where safe
rector.php gets the codeQuality set: declare(strict_types=1) in the 85
files where Rector finds no call relying on scalar type juggling (mostly
TCA and configuration, models, the content feed API and the import
command), "or die" as "|| die", instanceof instead of null checks for
object types, early returns and self:: in final classes.

Hotel::setRooms() goes instead of getting the property Rector wanted to
declare: nothing calls it, and there is no rooms column any more.

Checked locally: cache warmup, the ep:* commands load, frontend crawl
(~1,900 URLs) and the backchannel APIs render the same as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 11:15:38 +01:00
frommeandClaude Opus 5.5 f4556ee75c refactor: remove dead code via Rector
rector.php gets the deadCode set: @param/@return/@var tags that only
repeat native types, === true comparisons, three unused constructor
dependencies (DateService, ProductDataService, AjaxWatchlistController),
the unused reseller lookup in ContextProcessor and an always-false check
in AbstractMappedFinisher (trimExplode with removeEmpty never yields an
empty remote name).

Checked locally: frontend crawl (~1,900 URLs) and the backchannel APIs
render the same as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 11:10:20 +01:00
frommeandClaude Opus 5.5 9347112d6b refactor: private instead of protected where nothing relies on it, constructor promotion
Members (properties, methods, constants) become private when they are
declared in the class itself, no parent class or interface declares them,
no ancestor in packages/ uses them and the class has no subclass in
packages/: 115 members in 36 files. Extbase domain objects keep protected
properties, AbstractDomainObject sets them from the parent class.

With the properties private, Rector's constructor promotion now also
applies to the controllers and services that still assigned their
dependencies by hand (26 files).

Checked locally: frontend crawl (~1,900 URLs) and the backchannel APIs
render the same as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 11:05:45 +01:00
frommeandClaude Opus 5.5 b51fa4ddad refactor: PHP 8.5 language features via Rector
rector.php gets the PHP sets up to 8.5. Mostly constructor promotion with
readonly properties, typed class constants, ??=, arrow functions,
str_contains/str_starts_with, catch without unused variables, array_any
and #[\Override] on overridden properties; array_key_exists() keys are
cast to string (null keys are deprecated in PHP 8.5). Long promoted
constructor signatures are wrapped one parameter per line.

Checked locally: a crawl of ~1,900 frontend URLs over all sites and the
backchannel APIs (/api/hotel, /api/product, content feed manifest and
records of every type) render the same as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 10:59:53 +01:00
frommeandClaude Opus 5.5 96632edf44 fix: hotel and product images in the API no longer depend on cache order
The image services cached under images-<table>-<uid> without the field, so
the teaser images (teaser_images, rendered by the frontend) and the full
image lists for MyE&P (images, /api/hotel and /api/product) shared one
cache entry. Once a page had shown a hotel teaser, the API answered with
that single teaser image instead of all images. The field is now part of
the key.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 10:59:16 +01:00
frommeandClaude Opus 5.5 fe3af34063 fix: one-column teasers without an image no longer break the page
f:image threw when the teaser had no image (teaserImage false), and
production showed "Oops, an error occurred!" instead of the element, e.g.
three times on the snowzone privacy page. The image is now skipped like in
the disturber partials.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 10:52:11 +01:00
frommeandClaude Opus 5.5 3fa4a81e97 chore: remove the outdated copyright notices
The GPL headers named dreipunktnull and an old address and no longer
match the license of the code.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 10:41:24 +01:00
frommeandClaude Opus 5.5 4a3ef91dd0 chore: fileadmin is a shared dir of the TYPO3 13 deployment
fileadmin moves from the TYPO3 10 release
(ep-reisen-typo3/shared/public/fileadmin) to
ep-reisen-typo3-13/shared/public/fileadmin, so Deployer links it like the
other shared dirs (relative symlink) and the typo3:fileadmin task with
its absolute fileadmin_path goes.

deploy:shared creates a missing shared dir empty, which would leave the
sites without any file: deploy:check:fileadmin aborts the deploy before
anything happens on the server if the shared fileadmin is missing. Deploy
this only after the directory has been moved.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 09:48:17 +01:00
frommeandClaude Opus 5.5 ff45147964 chore: remove the one-off ep:scheduler-tasks command
It replaced the TYPO3 10 import tasks with scheduler tasks, which ran in
production on 2026-10-24 (tasks 26-28). The soft-deleted TYPO3 10 rows it
left are hard-deleted by hand on the database.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 09:44:32 +01:00
frommeandClaude Opus 5.5 386d89a1e0 chore: forms post to their own page instead of the form page type
The form page type (/form, 1704) existed because on the static pages of the
hybrid setup the page URL never reached TYPO3. With static rendering
retired, forms post to the page they are on, like the newsletter form:
the additionalParams in FormSetup.yaml, Page/Form.typoscript and the /form
entries of the PageType enhancers are gone.

Each form island gets the id form-island-<form identifier> (EXT:form
appends the content element uid), and Form.html targets and selects that
id instead of .form-island. With two forms on one page, a submission no
longer swaps in the first form's island.

Kept: hx-boost on the forms, HtmxRedirectMiddleware (Redirect finishers
under htmx) and /captcha (bw_captcha's image type).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 09:33:32 +01:00
frommeandClaude Opus 5.5 114a45aed3 chore: retire the snow report and search bar islands
They only served the static pages of the hybrid setup, which are retired:
TYPO3 13 renders search bar and snow reports in the page itself. Removed
are the page types 1705 (/snowreport) and 1706/1707 (/searchbar,
/searchbar-collapsed), their PageType enhancer entries and the id wrapper
of the snow report list that the static pages selected.

Also gone: the remarks about static pages and docroots in
ReferrerMiddleware and .htaccess.

The form page type (/form, 1704) stays for now; forms move off it in a
separate step.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 09:31:55 +01:00
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
frommeandClaude Opus 5.5 d9f74e271e fix: the date fields of the product teaser group plugin are datetime fields
settings.dateFrom and settings.dateTo were still type=input with eval=date.
TYPO3 13 only migrates renderType=inputDateTime to type=datetime, so these
were rendered as text inputs whose JavaScript expects an ISO 8601 value. It
got the stored timestamp (e.g. 1793491200), threw "Invalid ISO8601 DateTime
string", and the edit form of these plugin elements never finished loading.

The datetime element converts the stored timestamps itself and, without a
dbType, saves timestamps again, so stored values and the frontend stay as
they are.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 14:41:53 +01:00
frommeandClaude Opus 5.5 3f88229a70 feat: settings.php is shared on the server and no longer in git; production is leading
Every deploy replaced settings.php with the committed version, so changes
made in the production backend (extension configuration, install tool) were
lost with the next deploy or rollback. As LocalConfiguration.php on TYPO3 10,
it is now a shared file. typo3:settings:init seeds it once from the live
release; deploy:shared would otherwise seed it from the new release, i.e.
from git, and drop production's settings one last time.

The file is no longer tracked: it holds the encryptionKey and the install
tool password hash, and git has no use for it once production is leading.
Local copies stay in place (ignored).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 20:31:21 +02:00
fromme 6edd0fab6e feat: add logo in png format for email templates 2026-10-24 20:08:43 +02:00
frommeandClaude Opus 5.5 3c27cd84b9 fix: file names stay ASCII as on TYPO3 10 (UTF8filesystem off)
The fresh TYPO3 13 installation got UTF8filesystem = true from the core's
factory configuration; TYPO3 10 ran with the default false, so uploads
turned umlauts into ASCII (Bärenhütte.jpg -> Baerenhuette.jpg) and no file
in sys_file has a non-ASCII name. With true and no UTF-8 system locale,
escapeshellarg() drops those characters and image processing misses the
file (environment check warning). Back to false: the TYPO3 10 behaviour,
no locale needed, and file URLs and CDN paths stay free of encoded umlauts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 19:57:02 +02:00
frommeandClaude Opus 5.5 283f54c56c feat: EXT:staticfilecache serves cached pages without PHP again, as on TYPO3 10
Every page view goes through TYPO3 13 since the switch; staticfilecache
15.2 writes a gzip copy of each cacheable page, and the TYPO3 10 rewrite
rules in public/.htaccess let Apache deliver it directly (GET, no query
string, no backend login). Pages answer in about 2 ms instead of
0.5-4 s; static and dynamic output are identical for a sample of 22
sitemap URLs.

A flush of the pages cache group (travel data and snow report imports,
"clear frontend caches", deploys) removes the files; a page's own cache
lifetime still applies through the generated per-page .htaccess.

X-SFC-Tags is not passed on: with every record of a page as a tag it
reaches 100+ KB, more than Apache accepts from PHP-FPM, and it would
expose internal record ids. Debug headers stay off for the same reason.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 19:05:21 +02:00
frommeandClaude Opus 5.5 df3d823de4 feat: page, hash and rootline caches in Redis again, as on TYPO3 10
Since every page view goes through TYPO3 13, the page cache is on the hot
path; TYPO3 10 kept it in Redis, TYPO3 13 had it in MySQL. Same socket and
options as TYPO3 10, but databases 3-5 instead of 0-2, which may still
hold TYPO3 10 entries in a format TYPO3 13 must not read. DDEV gets the
redis add-on for the same setup locally.

Without a Redis connection the backend logs an alert and misses, so pages
still render (slower); a missing PHP redis extension, however, fails hard.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 18:51:57 +02:00
frommeandClaude Opus 5.5 520feb003f feat: scheduler runs the imports and the housekeeping tasks again
typo3/cms-scheduler 13.4 replaces the three direct cron jobs with one
scheduler:run cron job. ep:scheduler-tasks (one-off, idempotent) soft-deletes
the TYPO3 10 tasks TYPO3 13 cannot run (import task classes that are gone, one
groups.swiss task per house with the API keys in the database, the retired
E&P Events log) and adds "Execute console commands" tasks for ep:import-dates,
ep:import-snowreport and ep:groups-swiss with the TYPO3 10 schedules. The core
garbage collection, IP anonymization and file indexing tasks keep working as
they are; tx_epproducts_log is registered for garbage collection again.

The first execution is the next cron match, so adding the tasks never starts
an import or a groups.swiss export right away.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 18:27:50 +02:00
frommeandClaude Opus 5.5 f193d356cb feat: schneereisen.com redirects to www.ep-reisen.de
An alias of the ep-reisen vhost; path kept, 301.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 15:24:13 +02:00
frommeandClaude Opus 5.5 fcf04dbca7 chore: no delayed cache flush after deploys
Waiting 150 s at the end of every deploy is impractical. The flush right
after the switch stays; the window in which the previous release can
still fill the page cache is documented in deploy.php.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 15:03:23 +02:00
frommeandClaude Opus 5.5 837fca3b4c feat: unisnow.de and unisnow.com redirect to snowbreak.ep-reisen.de
Like unichamp.de: path kept, 302.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 14:58:09 +02:00
frommeandClaude Opus 5.5 fec781d634 feat: ep-reisen.com and ep-reisen.info redirect to www.ep-reisen.de
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 14:57:29 +02:00
frommeandClaude Opus 5.5 cd7d347006 feat: every ep-events.de URL redirects to the company trips page
E&P Events is retired; www and bare domain go to
www.ep-reisen.de/reisen-fuer-gruppen/gruppenreisen/firmenreisen/ (301).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 14:54:36 +02:00
frommeandClaude Opus 5.5 4b6dfdd390 fix: skiwochenenden.de redirects to www.ski-wochenenden.de
An alias of the ski-wochenenden vhost that the static docroot answered
regardless of the host; TYPO3 13 rejected it (trusted hosts pattern).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 14:50:47 +02:00
frommeandClaude Opus 5.5 4bfa0f258a fix: deploys flush the TYPO3 caches again after PHP has picked up the release
A vhost on current/public (alvac since today) keeps running the previous
release until PHP's realpath cache expires (120 s by default); pages it
rendered after the first flush stayed in the page cache with old paths.
A second flush 150 s after the switch (also after a rollback) removes them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 14:45:36 +02:00
frommeandClaude Opus 5.5 419ffe42b8 fix: TYPO3 caches are flushed after a rollback too
Pages cached while the other release was live survived the switch
(seen after the rollback test: alvac served old favicon paths until a
manual cache:flush). The flush task runs in current_path, which is the
new release after a deploy and the previous one after a rollback.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 14:44:08 +02:00
frommeandClaude Opus 5.5 873579d5b0 feat: legacy domain and /buchung redirects in TYPO3 13's .htaccess
Ported unchanged from TYPO3 10's public/.htaccess, whose rules the static
docroots carry until the vhosts point at TYPO3 13; Force SSL as there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 14:28:47 +02:00
frommeandClaude Opus 5.5 29f39fb440 fix: the release links fileadmin with a relative symlink
The vhosts answer 403 for files behind the absolute symlink (tested on
staging); Deployer's shared dirs are relative and work. The task now
also checks that the link resolves.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 14:23:26 +02:00
frommeandClaude Opus 5.5 e733a53935 fix: favicons and the schema.org logo use the published resource path
They pointed to /typo3conf/ext/..., which only exists in the static
docroots.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 13:54:57 +02:00
fromme 80fe3100ae Revert "fix: public resources answer under their TYPO3 10 path"
This reverts commit 9b90e32e6c.
2026-10-24 13:50:36 +02:00
frommeandClaude Opus 5.5 9b90e32e6c fix: public resources answer under their TYPO3 10 path
/typo3conf/ext/ep_theme|ep_products/Resources/Public/... is rewritten to
the _assets folder Composer publishes them to: the favicons and the
schema.org logo use that path, and so do links from outside. Today the
static docroots carry copies; after the vhost switch it would be 404.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 13:49:26 +02:00
frommeandClaude Opus 5.5 d7374c697d fix: blog pagination no longer highlights the link to page 1
The TYPO3 10 paginator styled it bg-button--secondary, which looked like
the current page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 13:47:06 +02:00
frommeandClaude Opus 5.5 99f9724574 feat: backend users see the TYPO3 13 frontend on staging.ep-reisen.de
Logged in to the staging backend (be_typo_user cookie of that host), the
frontend is no longer redirected to www, so pages can be checked before
they go live. Everyone else still gets the 301; the host sends
X-Robots-Tag: noindex.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 13:24:19 +02:00
frommeandClaude Opus 5.5 b48cbcd430 fix: new content element wizard offers only element types TYPO3 13 has
Occupancy calendar, event price table and deal of the week were not
ported (no visible uses), the newsletter form is retired.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 13:03:38 +02:00
frommeandClaude Opus 5.5 bf171285cd feat: FAQ overview element on TYPO3 13
Restored from a46918d (the FAQ page /skireisen/infos/allgemeine-infos/faqs/);
markup matches TYPO3 10.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 13:03:38 +02:00
frommeandClaude Opus 5.5 0cfd856894 chore: EXT:news default configuration
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 12:56:51 +02:00
frommeandClaude Opus 5.5 0b17a24edd feat: page module previews for teasers, disrupter, disturber, map and lineup
The TYPO3 10 drawItem hooks become one preview renderer registered per
CType in TCA; it renders the old templates from a46918d with the same
variables (f:format.html is not allowed in the backend: f:sanitize.html).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 12:56:22 +02:00
frommeandClaude Opus 5.5 44614a4a75 feat: blog on TYPO3 13 with EXT:news 14
News list, tag list and detail templates restored from a46918d; the
pagination widget is gone in news 14, so the TYPO3 10 paginator markup
now sits on the core sliding window pagination (pagination URLs change).
German labels for news committed. Production needs the news upgrade
wizards (the 5 plugin elements become the split news plugins).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 12:40:06 +02:00
frommeandClaude Opus 5.5 799dc03c75 feat: retire the group price calculator
MyE&P replaced it; TYPO3 only renders the price calendar. The element
stays in the database and renders nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 12:35:28 +02:00
frommeandClaude Opus 5.5 ad4089ae16 feat: lineup element on TYPO3 13
Restored from a46918d, TCA ported to TYPO3 13; markup matches TYPO3 10.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 12:29:07 +02:00
frommeandClaude Opus 5.5 a042eff41d feat: hotel, region, city, country and concept plugins, FAQ, team, maps and price table on TYPO3 13
Controllers, templates and FlexForms restored from a46918d, ported with
Rector/Fractor and by hand (settings and rows missing from older
records read as empty, as PHP 7.4 did). They stay list_type plugins for
now. The pages match TYPO3 10 apart from data that changed since (dates,
prices, edited texts).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 12:27:26 +02:00
frommeandClaude Opus 5.5 53faeccb70 feat: front page, event front page and popup page layouts on TYPO3 13
Restored from a46918d unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 12:05:01 +02:00
frommeandClaude Opus 5.5 2999abc16c feat: contact, text with image, YouTube/Vimeo video, disrupter and map elements on TYPO3 13
Restored from a46918d, TCA ported to TYPO3 13. Their markup matches
TYPO3 10 on every page that renders. Headers with the default layout
stay hidden, as on TYPO3 10.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 12:05:01 +02:00
frommeandClaude Opus 5.5 49b96df21a feat: retire the newsletter form, newsletter subscription and reseller export
The elements stay in the database and render nothing, so their pages no
longer fail on TYPO3 13.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 11:52:34 +02:00
frommeandClaude Opus 5.5 9668f08b4c chore: X-Ep-Compare header renders failing elements as placeholders in development
Lets the TYPO3 10 comparisons check elements on pages that still hold
content types without a rendering definition.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 11:51:55 +02:00
frommeandClaude Opus 5.5 301e89e60c feat: teasers and the left/event sidebar page layouts on TYPO3 13
Templates restored from a46918d unchanged; TCA and TypoScript were
already there. The teaser markup matches TYPO3 10 on every page that
renders (image titles lose trailing whitespace).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-24 11:51:55 +02:00
frommeandClaude Opus 5.5 dfed542af1 chore: remove the gridelements migration and the address check table
The grids are migrated in production, and the production database is the
master that is never replaced by a TYPO3 10 dump, so ep:migrate-gridelements
has nothing left to do; its tx_gridelements_* columns are dropped. The
address check forms are gone (pages removed), and so is the table that
stored their submissions: personal data that has not been processed since
2019.

The tables and columns themselves are dropped by hand: extension:setup only
adds and changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-23 18:29:29 +02:00
frommeandClaude Opus 5.5 d86efd72e3 fix: ep:migrate-gridelements stops cleanly when the report cannot be written
An unwritable --report path ended in a TypeError from fputcsv() after the
counts were printed. The command now says so and exits with an error before
anything is migrated. Page titles in the report include hidden pages
(Connection::select() applied the frontend restrictions).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-23 18:07:05 +02:00
frommeandClaude Opus 5.5 119364dcaa fix: reference index update no longer fails on the job application forms
referenceindex:update stopped at tt_content with "The Prototype
applicationform was not found": the backend and the CLI (form editor,
FlexForm of the form plugin, reference index) read module.tx_form, and the
form setup was only registered for the frontend (plugin.tx_form). TYPO3 10
set module.tx_form in the site TypoScript; TYPO3 13 reads it only from
global TypoScript, so ext_localconf.php registers FormSetup.yaml there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-23 17:47:57 +02:00
frommeandClaude Opus 5.5 4172ed978d feat: gridelements grids become container elements
TYPO3 13 does not know gridelements_pi1, so grids rendered nothing: the
inquiry form on /haus-christiler/ was missing. The five grid layouts are
container CTypes now (ep_container_halves, _thirds, _columns_quarters,
_half_and_two_quarters, _two_quarters_and_half; colPos 201-243) with the
TYPO3 10 templates ported. TwoQuartersAndHalf had no setup and output the
wrong columns; it renders its own columns now. As before, no container
inside these columns and the column rules of the old TSconfig apply.

ep:migrate-gridelements converts the data in place (plain SQL, idempotent,
--dry-run and --report): grids get their container CType, children in a
valid column move into it. Children in a column their layout lacks and
leftovers whose parent is no grid any more were invisible in TYPO3 10 and
are soft deleted. TYPO3 10 did not apply defaultHeaderType inside grids,
so text/textmedia/textpic children with header layout "default" get
"hidden" to keep their headers invisible.

Checked against TYPO3 10 on the 101 pages with grids that TYPO3 13 renders:
the container markup is identical; remaining differences are inside not
yet ported child elements.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-23 17:41:40 +02:00
422 changed files with 42269 additions and 5428 deletions

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
name: redis
repository: ddev/ddev-redis
version: v3.0.0
install_date: "2026-10-24T18:50:06+02:00"
project_files:
- docker-compose.redis.yaml
- redis/scripts/settings.ddev.redis.php
- redis/scripts/setup-drupal-settings.sh
- redis/scripts/setup-redis-optimized-config.sh
- redis/redis.conf
- redis/advanced.conf
- redis/append.conf
- redis/general.conf
- redis/io.conf
- redis/memory.conf
- redis/network.conf
- redis/security.conf
- redis/snapshots.conf
- commands/host/redis-backend
- commands/redis/redis-cli
- commands/redis/redis-flush
global_files: []
removal_actions:
- |
#ddev-description:Remove redis settings if applicable
files=(
"${DDEV_APPROOT}/${DDEV_DOCROOT}/sites/default/settings.ddev.redis.php"
"${DDEV_APPROOT}/.ddev/docker-compose.redis_extra.yaml"
)
for file in "${files[@]}"; do
if [ -f "$file" ]; then
if grep -q '#ddev-generated' "$file"; then
rm -f "$file"
else
echo "Unwilling to remove '$file' because it does not have #ddev-generated in it; you can manually delete it if it is safe to delete."
fi
fi
done
+44
View File
@@ -0,0 +1,44 @@
-- Removes personal data from a production dump. Run by `ddev anonymize-db`, which ddev also
-- runs after every `ddev import-db`. Safe to run more than once.
-- @devPasswordHash is set by the command before this file runs.
-- Tables renamed by the database analyzer in production keep their data until dropped there
SET @zzzTables = (
SELECT GROUP_CONCAT('`', table_name, '`')
FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name LIKE 'zzz\_deleted\_%'
);
SET @dropZzzTables = IF(@zzzTables IS NULL, 'DO 0', CONCAT('DROP TABLE ', @zzzTables));
PREPARE dropZzzTables FROM @dropZzzTables;
EXECUTE dropZzzTables;
DEALLOCATE PREPARE dropZzzTables;
-- Usernames stay, so everybody logs in locally as their own user (with the dev password)
UPDATE be_users SET
realName = IF('' = realName, '', CONCAT('BE User ', uid)),
email = IF('' = email, '', CONCAT('be-user-', uid, '@example.invalid')),
password = @devPasswordHash,
password_reset_token = '',
mfa = NULL;
UPDATE fe_users SET
username = CONCAT('fe-user-', uid),
password = @devPasswordHash,
name = IF('' = name, '', CONCAT('FE User ', uid)),
first_name = IF('' = first_name, '', 'Vorname'),
middle_name = '',
last_name = IF('' = last_name, '', CONCAT('Nachname ', uid)),
address = IF('' = address, '', 'Musterstraße 1'),
telephone = IF('' = telephone, '', '0000 000000'),
email = IF('' = email, '', CONCAT('fe-user-', uid, '@example.invalid')),
zip = IF('' = zip, '', '00000'),
city = IF('' = city, '', 'Musterstadt');
-- Sessions, locks, reports and logs: IP addresses, user agents, user data
TRUNCATE be_sessions;
TRUNCATE fe_sessions;
TRUNCATE sys_lockedrecords;
TRUNCATE sys_http_report;
TRUNCATE sys_log;
TRUNCATE tx_epproducts_log;
TRUNCATE tx_epevents_log;
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
#ddev-generated
## Description: Use a different key-value store for Redis
## Usage: redis-backend <image> [optimize]
## Example: ddev redis-backend redis-alpine optimize
# Keep in sync with the default image in docker-compose.redis.yaml
DEFAULT_REDIS_DOCKER_IMAGE="redis:8"
REDIS_DOCKER_IMAGE=${1:-}
REDIS_CONFIG=${2:-}
function show_help() {
cat <<EOF
Usage: ddev redis-backend <image|alias> [optimize]
Choose from predefined aliases, or provide any Redis-compatible Docker image.
Note that not every Docker image can work right away, and you may need to override
the "command:" in the docker-compose.redis_extra.yaml file
Available aliases:
redis ${DEFAULT_REDIS_DOCKER_IMAGE}
redis-alpine ${DEFAULT_REDIS_DOCKER_IMAGE}-alpine
valkey valkey/valkey:9
valkey-alpine valkey/valkey:9-alpine
Custom backend:
You can specify any Docker image, e.g.:
ddev redis-backend redis:7
Optional:
optimize Apply additional Redis configuration with resource limits
optimized Same as optimize
Examples:
ddev redis-backend redis-alpine optimize
ddev redis-backend valkey
ddev redis-backend redis:8.2-alpine
EOF
exit 0
}
function optimize_config() {
[[ "$REDIS_CONFIG" != "optimized" && "$REDIS_CONFIG" != "optimize" ]] && return
ddev dotenv set .ddev/.env.redis --redis-optimized=true
}
function change_hostname() {
[[ "${REDIS_HOSTNAME:-}" == "" ]] && return
ddev dotenv set .ddev/.env.redis --redis-hostname="$REDIS_HOSTNAME"
}
function cleanup() {
rm -f "$DDEV_APPROOT/.ddev/.env.redis"
rm -rf "$DDEV_APPROOT/.ddev/redis/"
rm -f "$DDEV_APPROOT/.ddev/docker-compose.redis.yaml" "$DDEV_APPROOT/.ddev/docker-compose.redis_extra.yaml"
redis_volume="ddev-$(ddev status -j | docker run -i --rm ddev/ddev-utilities jq -r '.raw.name')_redis"
if docker volume ls -q | grep -qw "$redis_volume"; then
ddev stop
docker volume rm "$redis_volume"
fi
}
function check_docker_image() {
echo "Pulling ${REDIS_DOCKER_IMAGE}..."
if ! docker pull "$REDIS_DOCKER_IMAGE"; then
echo >&2 "❌ Unable to pull ${REDIS_DOCKER_IMAGE}"
exit 2
fi
}
function use_docker_image() {
[[ "$REDIS_DOCKER_IMAGE" != "$DEFAULT_REDIS_DOCKER_IMAGE" ]] && ddev dotenv set .ddev/.env.redis --redis-docker-image="$REDIS_DOCKER_IMAGE"
# Reinstall the same add-on version that is installed now
local repo version flags=()
IFS='|' read -r repo version < <(ddev add-on list --installed -j 2>/dev/null | docker run -i --rm ddev/ddev-utilities jq -r '.raw[] | select(.Name=="redis") | "\(.Repository // "")|\(.Version // "")"' 2>/dev/null)
case "$version" in
# installed from a local directory
"") ;;
pr-*) flags=(--pr "${version#pr-}") ;;
v[0-9]*) flags=(--version "$version") ;;
# the default branch name, e.g. "main"
*) flags=(--default-branch) ;;
esac
ddev add-on get "${repo:-ddev/ddev-redis}" "${flags[@]}"
}
# "ddev add-on get" overwrites this file while it is running, so keep the main flow
# in a block, which bash reads completely before running it
{
case "$REDIS_DOCKER_IMAGE" in
redis)
REDIS_DOCKER_IMAGE="$DEFAULT_REDIS_DOCKER_IMAGE"
;;
redis-alpine)
REDIS_DOCKER_IMAGE="${DEFAULT_REDIS_DOCKER_IMAGE}-alpine"
;;
valkey)
REDIS_DOCKER_IMAGE="valkey/valkey:9"
REDIS_HOSTNAME="valkey"
;;
valkey-alpine)
REDIS_DOCKER_IMAGE="valkey/valkey:9-alpine"
REDIS_HOSTNAME="valkey"
;;
""|--help|-h)
show_help
;;
*)
# Allow unknown image, nothing to override
;;
esac
check_docker_image
cleanup
optimize_config
change_hostname
use_docker_image
echo
echo "✅ Redis backend: $REDIS_DOCKER_IMAGE"
if [[ "$REDIS_CONFIG" == "optimized" || "$REDIS_CONFIG" == "optimize" ]]; then
echo "⚙️ Redis config: optimized"
else
echo "⚙️ Redis config: default"
fi
echo
echo "📝 Commit the '.ddev' directory to version control"
echo
echo "🔄 Redis config available after 'ddev restart'"
exit 0
}
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env sh
#ddev-generated
## Description: Run redis-cli inside the Redis container
## Usage: redis-cli [flags] [args]
## Example: "ddev redis-cli KEYS *" or "ddev redis-cli INFO" or "ddev redis-cli --version"
## Aliases: redis
if [ -f /etc/redis/conf/security.conf ]; then
redis-cli -p 6379 -h "${REDIS_HOSTNAME:-redis}" -a redis --no-auth-warning $@
else
redis-cli -p 6379 -h "${REDIS_HOSTNAME:-redis}" $@
fi
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env sh
#ddev-generated
## Description: Flush all cache inside the Redis container
## Usage: redis-flush
## Example: "ddev redis-flush"
if [ -f /etc/redis/conf/security.conf ]; then
redis-cli -p 6379 -h "${REDIS_HOSTNAME:-redis}" -a redis --no-auth-warning FLUSHALL ASYNC
else
redis-cli -p 6379 -h "${REDIS_HOSTNAME:-redis}" FLUSHALL ASYNC
fi
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
## Description: Remove personal data from the imported production database
## Usage: anonymize-db
## Example: "ddev anonymize-db"
set -euo pipefail
DEV_PASSWORD=password
# TYPO3 verifies argon2i hashes with plain password_verify()
hash=$(php -r 'echo password_hash($argv[1], PASSWORD_ARGON2I);' "$DEV_PASSWORD")
{
echo "SET @devPasswordHash = '${hash}';"
cat /var/www/html/.ddev/anonymize-db.sql
} | mysql --host=db --user=db --password=db db
echo "Database anonymized. Backend users log in with their username and the password '${DEV_PASSWORD}'."
+4
View File
@@ -313,3 +313,7 @@ corepack_enable: false
#hooks:
# post-start:
# - exec: composer install -d /var/www/html
hooks:
post-import-db:
- exec: .ddev/commands/web/anonymize-db
+27
View File
@@ -0,0 +1,27 @@
#ddev-generated
services:
redis:
container_name: ddev-${DDEV_SITENAME}-redis
image: ${REDIS_DOCKER_IMAGE:-redis:8}
hostname: ${REDIS_HOSTNAME:-redis}
# These labels ensure this service is discoverable by ddev.
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: ${DDEV_APPROOT}
restart: "no"
expose:
- 6379
volumes:
- ".:/mnt/ddev_config"
- "ddev-global-cache:/mnt/ddev-global-cache"
- "./redis:/etc/redis/conf"
- "redis:/data"
command: /etc/redis/conf/redis.conf
x-ddev:
describe-url-port: |
Backend: ${REDIS_DOCKER_IMAGE:-redis:8}
describe-info: |
Pass: <none>
volumes:
redis:
+13
View File
@@ -0,0 +1,13 @@
# Redis configuration.
# #ddev-generated
# Example configuration files for reference:
# http://download.redis.io/redis-stable/redis.conf
# http://download.redis.io/redis-stable/sentinel.conf
maxmemory 2048mb
maxmemory-policy allkeys-lfu
# to disable Redis persistence, remove ddev-generated from this file,
# and uncomment the two lines below:
#appendonly no
#save ""
+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/
+16 -55
View File
@@ -1,65 +1,26 @@
# TYPO3 CMS Base Distribution
# E&P Reisen websites (TYPO3 13)
Get going quickly with TYPO3 CMS.
TYPO3 13 LTS for www.ep-reisen.de and its subsites (ski-boarderweek, snowzone, semesterende-skireisen,
ski-wochenenden, …). Products, hotels, regions and dates live in `packages/ep_products`, templates
and assets in `packages/ep_theme`. Dates, prices and availability come from **MyE&P** (`EP/myep`),
which in turn reads bpn-connect.
## Prerequisites
## Local setup
* PHP 8.2
* [Composer](https://getcomposer.org/download/)
DDEV project: `ddev start`, then `ddev composer install` and `ddev npm install && ddev npm run build`.
Rebuild the assets after template changes: Tailwind purges classes it does not find in the
templates.
## Quickstart
## Documentation
* `composer create-project typo3/cms-base-distribution project-name ^13`
* `cd project-name`
- [docs/cms-api.md](docs/cms-api.md): the API TYPO3 serves to MyE&P and the chatbot
(`/api/hotel`, `/api/product`, `/api/calendar-hotels`, `/api/content/*`).
- [docs/calendars.md](docs/calendars.md): price calendar, Belegungskalender and iCal feed: where
the data comes from, caching, and what to check when a calendar does not load.
Note that this distribution installs most, but not all of the TYPO3 CMS core extensions.
Depending on your need you might also want to install other TYPO3 extensions from
[packagist.org](https://packagist.org/?type=typo3-cms-framework).
## Deployment
### Setup
To start an interactive installation, you can do so by executing the following
command and then follow the wizard:
```bash
composer exec typo3 setup
```
### Setup unattended (optional)
If you're a more advanced user, you might want to leverage the unattended installation.
To do this, you need to execute the following command and substitute the arguments
with your own environment configuration.
```bash
export TYPO3_SETUP_ADMIN_PASSWORD=$(tr -dc "_A-Za-z0-9#=$()/" < /dev/urandom | head -c24)
composer exec -- typo3 setup \
--no-interaction \
--server-type=other \
--driver=sqlite \
--admin-username=admin \
--admin-email="[email protected]" \
--project-name="My TYPO3 Project" \
--create-site="http://localhost:8000/"
echo "Admin password: ${TYPO3_SETUP_ADMIN_PASSWORD}"
```
### Development server
While it's advised to use a more sophisticated web server such as
Apache 2 or Nginx, you can instantly run the project by using PHPs` built-in
[web server](https://secure.php.net/manual/en/features.commandline.webserver.php).
* `TYPO3_CONTEXT=Development php -S localhost:8000 -t public`
* open your browser at "http://localhost:8000"
Please be aware that the built-in web server is single threaded and only meant
to be used for development.
## Next steps
* [Getting Started with TYPO3](https://docs.typo3.org/permalink/t3start:start)
* [Create a Site Package](https://docs.typo3.org/permalink/t3sitepackage:start)
`deploy.php` (Deployer). A deploy runs `extension:setup`, resets OPcache and flushes all caches.
## License
+6 -2
View File
@@ -19,6 +19,9 @@
"ep/ep-products": "@dev",
"ep/ep-theme": "@dev",
"fluidtypo3/vhs": "^8.0",
"georgringer/news": "^14.1",
"helhum/typo3-console": "^8.2",
"lochmueller/staticfilecache": "^15.2",
"ssch/typo3-encore": "^7.0",
"typo3/cms-backend": "^13.4",
"typo3/cms-belog": "^13.4",
@@ -27,7 +30,6 @@
"typo3/cms-dashboard": "^13.4",
"typo3/cms-extbase": "^13.4",
"typo3/cms-extensionmanager": "^13.4",
"typo3/cms-felogin": "^13.4",
"typo3/cms-filelist": "^13.4",
"typo3/cms-fluid": "^13.4",
"typo3/cms-fluid-styled-content": "^13.4",
@@ -41,12 +43,14 @@
"typo3/cms-recycler": "^13.4",
"typo3/cms-redirects": "^13.4",
"typo3/cms-rte-ckeditor": "^13.4",
"typo3/cms-scheduler": "^13.4",
"typo3/cms-seo": "^13.4",
"typo3/cms-setup": "^13.4",
"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
+1260 -482
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
/*/settings.yaml
+2
View File
@@ -20,3 +20,5 @@ languages:
languageId: 0
rootPageId: 2458
websiteTitle: ''
dependencies:
- ep/theme
+2
View File
@@ -20,3 +20,5 @@ languages:
languageId: 0
rootPageId: 2457
websiteTitle: ''
dependencies:
- ep/theme
+2 -4
View File
@@ -282,11 +282,7 @@ routeEnhancers:
/: 0
/json: 1701
/html: 1702
/form: 1704
/captcha: 3413
searchbar: 1706
searchbar-collapsed: 1707
/snowreport: 1705
.json: 1803
.ical: 1710
sitemap.xml: 1533906435
@@ -298,3 +294,5 @@ routes:
type: staticText
content: "User-agent: *\r\nDisallow: /typo3/\r\nDisallow: /typo3_src/\r\nDisallow: /ajax/\r\nAllow: /typo3/sysext/frontend/Resources/Public/*\r\n"
websiteTitle: ''
dependencies:
- ep/theme
+2 -2
View File
@@ -85,12 +85,12 @@ routeEnhancers:
default: ''
index: ''
map:
/form: 1704
/captcha: 3413
/snowreport: 1705
routes:
-
route: robots.txt
type: staticText
content: "User-agent: *\r\nDisallow: /typo3/\r\nDisallow: /typo3_src/\r\nAllow: /typo3/sysext/frontend/Resources/Public/*\r\n"
websiteTitle: 'Semsterende Skireisen'
dependencies:
- ep/theme
+2 -2
View File
@@ -66,9 +66,7 @@ routeEnhancers:
/: 0
/json: 1701
/html: 1702
/form: 1704
/captcha: 3413
/snowreport: 1705
sitemap.xml: 1533906435
yoast-snippetpreview.json: 1480321830
routes:
@@ -77,3 +75,5 @@ routes:
type: staticText
content: "User-agent: *\r\nDisallow: /typo3/\r\nDisallow: /typo3_src/\r\nAllow: /typo3/sysext/frontend/Resources/Public/*\r\n"
websiteTitle: ''
dependencies:
- ep/theme
+2 -1
View File
@@ -50,7 +50,6 @@ routeEnhancers:
/: 0
/json: 1701
/html: 1702
/form: 1704
/captcha: 3413
sitemap.xml: 1533906435
yoast-snippetpreview.json: 1480321830
@@ -60,3 +59,5 @@ routes:
type: staticText
content: "User-agent: *\r\nDisallow: /typo3/\r\nDisallow: /typo3_src/\r\nAllow: /typo3/sysext/frontend/Resources/Public/*\r\n"
websiteTitle: 'Ski-Wochenenden mit E&P Reisen und Events'
dependencies:
- ep/theme
+2 -2
View File
@@ -50,9 +50,7 @@ routeEnhancers:
/: 0
/json: 1701
/html: 1702
/form: 1704
/captcha: 3413
/snowreport: 1705
sitemap.xml: 1533906435
yoast-snippetpreview.json: 1480321830
routes:
@@ -60,3 +58,5 @@ routes:
route: robots.txt
type: staticText
content: "User-agent: *\r\nDisallow: /typo3/\r\nDisallow: /typo3_src/\r\nAllow: /typo3/sysext/frontend/Resources/Public/*\r\n"
dependencies:
- ep/theme
+2 -2
View File
@@ -50,9 +50,7 @@ routeEnhancers:
/: 0
/json: 1701
/html: 1702
/form: 1704
/captcha: 3413
/snowreport: 1705
sitemap.xml: 1533906435
yoast-snippetpreview.json: 1480321830
routes:
@@ -60,3 +58,5 @@ routes:
route: robots.txt
type: staticText
content: "User-agent: *\r\nDisallow: /typo3/\r\nDisallow: /typo3_src/\r\nAllow: /typo3/sysext/frontend/Resources/Public/*\r\n"
dependencies:
- ep/theme
+1
View File
@@ -1,2 +1,3 @@
/secrets.php
/drushrc.php
/settings.php
+34
View File
@@ -64,6 +64,40 @@ if (getenv('IS_DDEV_PROJECT') == 'true') {
);
}
// Redis caches as on TYPO3 10, but in databases 3-5: 0-2 may still hold TYPO3 10 entries. Without a
// connection the backend only logs an alert and every lookup is a miss, so pages still render.
$redisOptions = getenv('IS_DDEV_PROJECT') == 'true'
? ['hostname' => 'redis', 'port' => 6379]
: ['hostname' => '/run/redis_eptypo3/redis.sock', 'port' => 0];
$redisOptions['connectionTimeout'] = 2;
// These replace the definitions from settings.php wholesale, so 'groups' and the compression and
// lifetime options have to be repeated here. Each cache has its own database: RedisBackend::flush()
// issues FLUSHDB.
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['pages'] = [
'frontend' => \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend::class,
'backend' => \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class,
'options' => $redisOptions + ['database' => 3, 'compression' => true],
'groups' => ['pages'],
];
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['hash'] = [
'frontend' => \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend::class,
'backend' => \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class,
'options' => $redisOptions + ['database' => 4],
'groups' => ['pages'],
];
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['rootline'] = [
'frontend' => \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend::class,
'backend' => \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class,
'options' => $redisOptions + ['database' => 5, 'compression' => true, 'defaultLifetime' => 2592000],
'groups' => ['pages'],
];
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)) {
-157
View File
@@ -1,157 +0,0 @@
<?php
return [
'BE' => [
'debug' => false,
'installToolPassword' => '$argon2i$v=19$m=65536,t=16,p=1$NVBiTmJvdEljbWY0WmdjUg$YRkMEpjn+hVLAZWWrrU9wIhVgZ3pPhSnPQyB/7+7Z1g',
'passwordHashing' => [
'className' => 'TYPO3\\CMS\\Core\\Crypto\\PasswordHashing\\Argon2iPasswordHash',
'options' => [],
],
],
'DB' => [
'Connections' => [
'Default' => [
'charset' => 'utf8mb4',
'dbname' => 'db',
'defaultTableOptions' => [
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
],
'driver' => 'mysqli',
'host' => 'db',
'password' => 'db',
'port' => 3306,
'user' => 'db',
],
],
],
'EXTCONF' => [
'lang' => [
'availableLanguages' => [
'de',
],
],
],
'EXTENSIONS' => [
'backend' => [
'backendFavicon' => '',
'backendLogo' => '',
'loginBackgroundImage' => '',
'loginFootnote' => '',
'loginHighlightColor' => '',
'loginLogo' => '',
'loginLogoAlt' => '',
],
'ep_products' => [
'bpnConnectApiBaseUrl' => '',
'bpnConnectApiKey' => '',
'bpnConnectApiMaxDuration' => '15',
'bpnConnectApiTimeout' => '5',
'cmsApiKey' => '',
'contentFeedEnabled' => '1',
'contentFeedExcludedPages' => '634',
'contentFeedLanguages' => '0',
'contentFeedNewsDetailPages' => 'ep-reisen:1392',
'contentFeedSites' => 'ep-reisen,ser,ski-boarderweek,ski-wochenenden,snowzone',
'importStoragePid' => '11',
'myEpApiBaseUrl' => '',
'myEpApiClientId' => '',
'myEpApiClientSecret' => '',
'myEpApiMaxDuration' => '15',
'myEpApiTimeout' => '5',
'myEpApiUrlAuthorize' => '',
'myEpApiUrlToken' => '',
'myEpStagingUrl' => 'https://my.ep-reisen.net',
'myEpUrl' => 'https://my.ep-reisen.de/',
'searchPageUid' => '0',
],
'ep_theme' => [
'cdnBaseUrl' => 'https://cdn.ep-reisen.de/',
],
'extensionmanager' => [
'automaticInstallation' => '1',
'offlineMode' => '0',
],
'redirects' => [
'showCheckIntegrityInfoInReports' => '1',
'showCheckIntegrityInfoInReportsSeconds' => '86400',
],
],
'FE' => [
'cacheHash' => [
'enforceValidation' => true,
],
'debug' => false,
'disableNoCacheParameter' => true,
'passwordHashing' => [
'className' => 'TYPO3\\CMS\\Core\\Crypto\\PasswordHashing\\Argon2iPasswordHash',
'options' => [],
],
'compressionLevel' => 0,
],
'GFX' => [
'processor' => 'GraphicsMagick',
'processor_effects' => false,
'processor_enabled' => true,
'processor_path' => '/usr/bin/',
],
'LOG' => [
'TYPO3' => [
'CMS' => [
'deprecations' => [
'writerConfiguration' => [
'notice' => [
'TYPO3\CMS\Core\Log\Writer\FileWriter' => [
'disabled' => true,
],
],
],
],
],
],
],
'MAIL' => [
'transport' => 'sendmail',
'transport_sendmail_command' => '/usr/sbin/sendmail -t -i',
'transport_smtp_encrypt' => '',
'transport_smtp_password' => '',
'transport_smtp_server' => '',
'transport_smtp_username' => '',
],
'SYS' => [
'UTF8filesystem' => true,
'caching' => [
'cacheConfigurations' => [
'hash' => [
'backend' => 'TYPO3\\CMS\\Core\\Cache\\Backend\\Typo3DatabaseBackend',
],
'pages' => [
'backend' => 'TYPO3\\CMS\\Core\\Cache\\Backend\\Typo3DatabaseBackend',
'options' => [
'compression' => true,
],
],
'rootline' => [
'backend' => 'TYPO3\\CMS\\Core\\Cache\\Backend\\Typo3DatabaseBackend',
'options' => [
'compression' => true,
],
],
],
],
'devIPmask' => '',
'displayErrors' => 0,
'encryptionKey' => '0ad297595e43cb5f668bdf7878eaea7002a4193fd26d2742a3a708b4b0715bcfabdae84a47ca8517d55732d9ea72a26a',
'exceptionalErrors' => 4096,
'features' => [
'extbase.consistentDateTimeHandling' => true,
'frontend.cache.autoTagging' => true,
'security.system.enforceAllowedFileExtensions' => true,
],
'sitename' => 'E&P',
'systemMaintainers' => [
1,
],
'trustedHostsPattern' => '(?:[a-z0-9-]+\\.)?(?:ep-reisen\\.de|ski-boarderweek\\.(?:de|com|eu)|skiboarderweek\\.(?:de|eu)|snowzone\\.net|unichamp\\.de|snow-break\\.(?:de|com)|semesterende-skireisen\\.de|alpinevacation\\.ch|cllt-touristik\\.at|ep-events\\.de|ski-wochenenden\\.de)',
],
];
+55 -102
View File
@@ -2,32 +2,17 @@
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
set('fileadmin_path', '/usr/home/eptypo3/public_html/ep-reisen-typo3/shared/public/fileadmin');
set('shared_dirs', [
'{{typo3/public_dir}}/fileadmin',
'{{typo3/public_dir}}/typo3temp/assets',
'var/lock',
'var/log',
@@ -36,8 +21,16 @@ set('shared_dirs', [
// Baseline of the MyE&P import: without it every deploy would trigger a full re-import
'var/transient/ep_products_import',
]);
// Not in git, production is leading (encryptionKey, install tool, backend changes; the site
// settings are edited in the backend's Settings module, one settings.yaml per site)
set('site_settings_files', array_map(
static fn(string $config): string => 'config/sites/' . basename(dirname($config)) . '/settings.yaml',
glob(__DIR__ . '/config/sites/*/config.yaml')
));
set('shared_files', [
'config/system/secrets.php',
'config/system/settings.php',
...get('site_settings_files'),
]);
set('writable_dirs', [
'{{typo3/public_dir}}/typo3temp/assets',
@@ -48,6 +41,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',
@@ -63,7 +63,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
@@ -78,106 +78,58 @@ 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;
// deploy:shared would create a missing shared dir empty, and the sites would lose every file
task('deploy:check:fileadmin', function () {
if (!test('[ -d {{deploy_path}}/shared/{{typo3/public_dir}}/fileadmin ]')) {
throw new \RuntimeException(parse('{{deploy_path}}/shared/{{typo3/public_dir}}/fileadmin is missing'));
}
});
// The same for a site's settings.yaml: an empty one silently resets the site to the default settings
// (navigation, storage folders, ...). A new site's file is put in place by hand first.
task('deploy:check:site-settings', function () {
foreach (get('site_settings_files') as $file) {
if (!test("[ -s {{deploy_path}}/shared/$file ]")) {
throw new \RuntimeException(parse("{{deploy_path}}/shared/$file is missing or empty"));
}
}
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');
}
});
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('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}} {{release_path}}/{{bin/typo3}} cache:flush');
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}}');
run('ln -sfn {{fileadmin_path}} {{release_path}}/{{typo3/public_dir}}/fileadmin');
});
desc('Deploys TYPO3 13');
task('deploy', [
'deploy:info',
// Build first, so a failing build aborts before the server is touched at all.
'build',
'deploy:check:fileadmin',
'deploy:check:site-settings',
// 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',
'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',
@@ -186,9 +138,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 www.ep-reisen.de/typo3temp, which
// is the static docroot. Not needed either: the front controller stubs in the static docroots resolve
// "current" with readlink() on every request, so a new release is always loaded under new paths.
// 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');
after('deploy:failed', 'deploy:unlock');
// Pages cached by the other release must not survive a rollback
after('rollback', 'cachetool:clear:opcache');
after('rollback', 'typo3:cache:flush');
+116
View File
@@ -0,0 +1,116 @@
# Availability and prices on product pages
TYPO3 shows three things built from contingent data. None of them talks to bpn-connect directly:
all go through **MyE&P**, which keeps a local snapshot of bpn-connect's availability and holds the
group house prices.
| What | Where | Shown for | MyE&P endpoint |
|---|---|---|---|
| **Price calendar** + price tables | product detail, tab "Termine/Preise" | products with a calendar hotel and season **summer** (`s`) | `contingents/calendar`, `contingents/prices` |
| **Belegungskalender** (availability calendar) | product detail, sidebar | products with a calendar hotel and season **not summer** | `contingents/availability` (+ `contingents/calendar` for the months to leave out) |
| **iCal feed** for booking portals | `https://www.ep-reisen.de/contingents/<hotel>.ical` | every hotel | `contingents/availability` |
So every product with a calendar hotel shows exactly one calendar. The MyE&P side is documented in
its `docs/api-consumer-guide.md` (`EP/myep`).
## The calendar hotel field
A product's **calendar hotel** (`calendar_hotel`) decides whose availability and prices it shows.
TYPO3 sends that hotel's **code** to MyE&P, which only understands **BusPro calendar codes** (the
codes bpn-connect knows, e.g. `DGS`, `LPJ`, `DKS`).
Most hotel records carry the BusPro code. Group houses also have **content records** of their own
(`DKSGRP`, `LPJGRP`, `SERASCSO`, …) whose code is the **CMS code** MyE&P uses to fetch content via
`/api/hotel`. These must **not** be used as calendar hotel: MyE&P and bpn-connect do not know
them, and the calendar shows its error message. This broke three example trips until 2026-10-29.
| Use as calendar hotel | Not |
|---|---|
| `DKS` Sportclub Schweizerhaus (uid 193) | `DKSGRP` Davos Klosters - Gruppenhaus Schweizerhaus |
| `LPJ` Sportclub Jenatsch (uid 37) | `LPJGRP` Lenzerheide - Gruppenhaus Jenatsch |
`/api/calendar-hotels` lists all calendar hotels in use; every code in it should be a BusPro code.
A hidden hotel record counts as no calendar hotel: the product shows no calendar and the code is
not in the list (e.g. `ASB` Sportclub Bader).
## Price calendar (summer)
- `Product/Detail.html`, section `Dates`, partial `Product/PriceCalendar`;
`AjaxCalendarController::priceCalendarAction` → `contingents/calendar` (status and price per
day), `priceTableAction` → `contingents/prices` for the current and the next year (loaded when
scrolled into view).
- Only works for hotels that are an **accommodation with prices** in MyE&P (summer group houses).
## Belegungskalender (winter)
- `Product/Detail.html`, section `AvailabilityCalendar`, loaded by htmx when it scrolls into view
(`intersect once`, so the hidden copy of the sidebar on the other breakpoint does not load);
`AjaxCalendarController::availabilityAction`, template `AjaxCalendar/Availability.html`,
month select `calendar_controller.js`.
- All months in one response; the select only switches between them. 12 months from the current
one, extended to the last month MyE&P has data for.
- **Months with prices are left out**: they are summer, covered by the price calendar. Only summer
months have prices today; that is the whole distinction. If `contingents/calendar` fails, all
months are shown (not cached).
- Colours as on TYPO3 10: green available (`OK`, and days without data), yellow on request
(`ON_REQUEST`), red not available (`BLOCKED`), past days grey; a status change from the previous
day is drawn as a diagonal. The `availability-*` classes are built in PHP, so they are safelisted
in `tailwind.config.js`; without that Tailwind purges them.
- Works for every hotel MyE&P syncs, with or without an accommodation (see below).
## iCal feed
- `IcalController::contingentsAction`, `IcalService`; route `/contingents/{hotel}.ical` on the
`ep-reisen` site (`config/sites/ep-reisen/config.yaml`, page 9, type 1710), `{hotel}` is the
hotel's path segment.
- One `Unavailable` event per run of `BLOCKED` or `ON_REQUEST` days with the same status, as on
TYPO3 10.
- Never cached. When MyE&P has no availability it answers `503` with `Retry-After: 900` instead of
an empty calendar, which the portals would read as "every day free"; they keep their last import.
## Which hotels MyE&P has availability for
MyE&P syncs its snapshot from bpn-connect every 15 minutes during the day and hourly at night, for
the calendar codes of its accommodations **plus** every code in TYPO3's `/api/calendar-hotels`
(see [cms-api.md](cms-api.md)). So a hotel offered only in winter needs nothing in MyE&P: setting
it as calendar hotel of a visible product is enough; it is synced with the next run.
`contingents/availability` answers:
- `400` for a code that is not synced (not an accommodation, not in TYPO3's list),
- `502` when the hotel's snapshot is missing or older than 6 hours (sync failing),
- `200` with one entry per known day otherwise.
`contingents/calendar` and `contingents/prices` additionally need an accommodation with prices.
MyE&P's `GET /api/contingents/status` lists every synced code with its source, prices, last sync
and last error: the first place to look when a calendar does not load.
## Caching
- The AJAX responses (price calendar, price tables, Belegungskalender) are page-cached on the AJAX
page for at most **1 hour** (`cache_period = 3600`) and never past midnight
(`cache_clearAtMidnight`, past days must turn grey). Browsers do not cache them
(`sendCacheHeaders = 0`).
- **Errors are not cached** (`disablePageCache()`): the next visitor triggers a new attempt.
- Every AJAX URL carries a cHash (required for `tx_epproducts_ajax`), so each hotel and year has its
own cache entry.
- Entries are tagged `ep_hotel_<code>`. Nothing flushes them automatically; a deploy flushes all
caches. To refresh by hand: `vendor/bin/typo3 cache:flush --group pages`, or flush the tag via
the `CacheManager` for a single hotel.
- On top of that comes MyE&P's sync interval, so a calendar is at most about 1¼ hours behind
bpn-connect.
## When a calendar does not load
The message is "Der Kalender konnte aktuell nicht geladen werden."; the iCal feed answers `503`.
| Cause | How to tell | Fix |
|---|---|---|
| Calendar hotel has a CMS code (`…GRP`, `…SO`) | code not BusPro; MyE&P `400`, bpn-connect `404` | set the hotel record with the BusPro code as calendar hotel |
| Code not synced by MyE&P | not in `contingents/status`; MyE&P `400` | check that the product and its calendar hotel are visible (then it is in `/api/calendar-hotels`) |
| Sync failing for the hotel | `contingents/status`: `available: false`, `lastError` | MyE&P / bpn-connect |
| MyE&P unreachable or its OAuth failing | all hotels fail; TYPO3 log `EP.EpProducts.MyEP.ApiClient` | MyE&P |
| Price calendar only: no accommodation or no prices | MyE&P `400` / empty | prices are maintained in MyE&P's Groups admin |
TYPO3 logs every failed MyE&P call in `var/log/typo3_*.log` (component
`EP.EpProducts.MyEP.ApiClient`, for the iCal feed also `EP.EpProducts.Service.IcalService`).
+101
View File
@@ -0,0 +1,101 @@
# CMS API
Endpoints TYPO3 serves to other systems: **MyE&P** (hotel and product content, calendar hotels)
and the **chatbot** (content feed). They are answered by `EP\EpProducts\Middleware\ApiMiddleware`
before the page resolver, so they do not depend on a page and are never page-cached.
The other side of these contracts is documented in MyE&P's `docs/api-consumer-guide.md`
(`EP/myep`).
## Authentication
All endpoints need the shared key `cmsApiKey` from the `ep_products` extension configuration
(Settings → Extension Configuration → ep_products, tab ContentFeed):
- header `X-Api-Key: <key>` (preferred, query strings end up in access logs), or
- query parameter `?key=<key>`.
A missing or wrong key answers `403 {"message": "Invalid or missing api key"}`. An empty
`cmsApiKey` denies every request. MyE&P sends the key as `APP_CMS_API_KEY`; both sides must be
changed together.
## `GET /api/hotel?hotel=<code>`
Content of one hotel, for MyE&P's group house pages and booking flow.
- `hotel`: the TYPO3 hotel code or one of its matchcode aliases. In MyE&P this is the
accommodation's **CMS code** (e.g. `DGSGRP`), which can differ from its calendar code.
- `400` without `hotel`, `404 {"message": "Hotel not found"}` for an unknown code.
```json
{
"name": "…", "code": "…",
"description": "…", "features": "…", "room_types": "…",
"additional_information": "…", "address": "…",
"images": {"original": [{"url": "…", "alt": "…", "title": "…"}], "resized": […]},
"icons": […],
"region": {"name": "…", "latitude": …, "longitude": …, "webcam": "…", "ski_area": "…",
"ski_area_extended": "…", "description": "…", "news": "…", "length": …,
"altitude": …, "lifts": …, "images": {…}, "region_maps": [{"url": "…", "alt": "…", "title": "…"}]}
}
```
## `GET /api/product?product=<code>[&hotel=<code>]`
Content of one product, optionally with a hotel.
- `product`: the product code. `400` without it, `404 {"message": "Product not found"}` for an
unknown code.
- `hotel` (optional): adds `hotel` with the same content as `/api/hotel`.
```json
{
"product": {"name": "…", "code": "…", "images": {…}},
"country": {"name": "…"},
"region": {…},
"city": {"name": "…"},
"hotel": {…}
}
```
## `GET /api/calendar-hotels`
The calendar hotel of every visible product: the hotel codes whose availability TYPO3 shows
(iCal feed, availability calendar, price calendar). MyE&P syncs its contingent snapshot for
these codes in addition to its accommodations, so hotels offered only in winter, which have no
accommodation in MyE&P, have availability too. See [calendars.md](calendars.md).
- Only products and hotels that are not hidden or deleted; hotels without a code are left out.
- Sorted by code; products by uid. Names have line breaks collapsed.
- `season`: `"s"` (summer), `"w"` (winter) or `null`.
```json
[
{"code": "DKI", "name": "Sportclub Schwendi", "products": [
{"uid": 155, "name": "Sportclub Schwendi", "season": "w"},
{"uid": 362, "name": "Gruppenhaus Schwendi", "season": "s"}
]}
]
```
Every code in this list should be a **BusPro calendar code**. A group house's content record
(`…GRP`, `…SO`) set as a product's calendar hotel puts its CMS code in here, which bpn-connect does
not know (see [calendars.md](calendars.md#the-calendar-hotel-field)).
## `GET /api/content/manifest` and `GET /api/content/records?type=<type>&uids=<uids>`
The content feed for the chatbot (`EP\EpProducts\ContentFeed\ContentFeedApi`).
- `manifest`: everything the feed publishes (pages of the configured sites and records), with a
modification timestamp per item. Cached for 10 minutes.
- `records`: up to 100 records of one type per request. `type` is one of `product`, `hotel`,
`region`, `country`, `city`, `concept`, `news`; `uids` comma-separated. Uids that are no longer
eligible are left out, not answered with an error (the consumer reads them as deleted).
- `400` for an unknown type, no uids or more than 100 uids; `404` for other paths.
- `503` with `Retry-After: 3600` when the feed is switched off (`contentFeedEnabled = 0`, the kill
switch for when the host is struggling), `503` when `contentFeedSites` is empty. The consumer
keeps what it has on a 503; nothing is deleted on the chatbot side.
Configuration (extension configuration `ep_products`, tab ContentFeed): `contentFeedEnabled`,
`contentFeedSites`, `contentFeedLanguages`, `contentFeedExcludedPages`,
`contentFeedNewsDetailPages`.
@@ -1,32 +1,10 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Command;
/***************************************************************
*
* 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\Service\ProductImport;
use EP\EpProducts\Service\DateImportService;
use EP\EpProducts\Service\ProductImportSource;
use Symfony\Component\Console\Attribute\AsCommand;
@@ -38,41 +16,22 @@ use Symfony\Component\Console\Output\OutputInterface;
class DateCommandController extends Command
{
/**
* @var DateImportService
*/
protected $dateImportService;
/**
* @var ProductImportSource
*/
protected $importSource;
/**
* @param DateImportService $importService
* @param ProductImportSource $importSource
*/
public function __construct
(
DateImportService $importService,
ProductImportSource $importSource
private readonly DateImportService $dateImportService,
private readonly ProductImportSource $importSource
)
{
$this->dateImportService = $importService;
$this->importSource = $importSource;
parent::__construct();
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @throws \Doctrine\DBAL\DBALException
*/
public function execute(InputInterface $input, OutputInterface $output): int
{
$import = $this->importSource->acquire();
if ($import === null) {
if (!$import instanceof ProductImport) {
$output->writeln('No new import available.');
return 0;
}
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\ContentFeed;
use EP\EpProducts\Http\ApiKeyAuthenticator;
@@ -19,53 +21,29 @@ class ContentFeedApi
{
public const PATH_PREFIX = '/api/content/';
private const MANIFEST_CACHE_IDENTIFIER = 'content_feed_manifest';
private const string MANIFEST_CACHE_IDENTIFIER = 'content_feed_manifest';
// The consumer polls every 15 minutes, so this caps the build at once per poll however
// often it retries. Edits reach the chatbot within about 25 minutes.
private const MANIFEST_CACHE_LIFETIME = 600;
private const int MANIFEST_CACHE_LIFETIME = 600;
private const MAX_RECORDS_PER_REQUEST = 100;
private const int MAX_RECORDS_PER_REQUEST = 100;
private const DISABLED_RETRY_AFTER = 3600;
private const int DISABLED_RETRY_AFTER = 3600;
private const JSON_FLAGS = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
/**
* @var ApiKeyAuthenticator
*/
private $authenticator;
/**
* @var ContentFeedConfiguration
*/
private $configuration;
/**
* @var ManifestBuilder
*/
private $manifestBuilder;
/**
* @var RecordSerializer
*/
private $recordSerializer;
/**
* @var FrontendInterface
*/
private $cache;
public function __construct(
ApiKeyAuthenticator $authenticator,
ContentFeedConfiguration $configuration,
ManifestBuilder $manifestBuilder,
RecordSerializer $recordSerializer,
private readonly ApiKeyAuthenticator $authenticator,
private readonly ContentFeedConfiguration $configuration,
private readonly ManifestBuilder $manifestBuilder,
private readonly RecordSerializer $recordSerializer,
CacheManager $cacheManager
) {
$this->authenticator = $authenticator;
$this->configuration = $configuration;
$this->manifestBuilder = $manifestBuilder;
$this->recordSerializer = $recordSerializer;
$this->cache = $cacheManager->getCache('ep_products_cache');
}
@@ -112,9 +90,7 @@ class ContentFeedApi
$uids = array_values(array_unique(array_filter(
GeneralUtility::intExplode(',', (string) ($request->getQueryParams()['uids'] ?? ''), true),
function (int $uid): bool {
return $uid > 0;
}
fn(int $uid): bool => $uid > 0
)));
if ([] === $uids) {
@@ -14,10 +14,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
*/
class ContentFeedConfiguration
{
/**
* @var array
*/
private $configuration;
private array $configuration;
public function __construct(ExtensionConfiguration $extensionConfiguration)
{
@@ -16,13 +16,13 @@ class ManifestBuilder
{
public const CONTENT_PAGE_TYPE = 1730;
private const DOKTYPE_DEFAULT = 1;
private const int DOKTYPE_DEFAULT = 1;
// Recycler and backend user section: nothing below them is public.
private const DOKTYPES_NOT_TRAVERSED = [6, 255];
private const array DOKTYPES_NOT_TRAVERSED = [6, 255];
// l18n_cfg bit 1: "hide default translation of page".
private const L18N_HIDE_DEFAULT_LANGUAGE = 1;
private const int L18N_HIDE_DEFAULT_LANGUAGE = 1;
private const RECORD_FIELDS = [
private const array RECORD_FIELDS = [
'product' => ['uid', 'tstamp', 'detail_page'],
'hotel' => ['uid', 'tstamp', 'detail_page'],
'region' => ['uid', 'tstamp', 'detail_page'],
@@ -32,43 +32,13 @@ class ManifestBuilder
'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
private readonly PageTree $pageTree,
private readonly UrlResolver $urlResolver,
private readonly RecordFinder $recordFinder,
private readonly ContentFeedConfiguration $configuration,
private readonly ConnectionPool $connectionPool,
) {
$this->pageTree = $pageTree;
$this->urlResolver = $urlResolver;
$this->recordFinder = $recordFinder;
$this->configuration = $configuration;
$this->connectionPool = $connectionPool;
}
public function build(): array
@@ -78,7 +48,7 @@ class ManifestBuilder
return [
'generatedAt' => date('c'),
'sites' => array_map([$this, 'describeSite'], $sites),
'sites' => array_map($this->describeSite(...), $sites),
'items' => array_merge($this->buildPageItems($sites, $this->collectDetailPages()), $records),
];
}
@@ -211,8 +181,14 @@ class ManifestBuilder
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
{
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.
@@ -278,7 +254,7 @@ class ManifestBuilder
'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"
"SUM(CASE WHEN deleted = 0 AND hidden = 0 AND CType <> 'list' AND CType NOT LIKE 'epproducts\\_%' THEN 1 ELSE 0 END) AS text_elements"
)
->from('tt_content')->groupBy('pid', 'sys_language_uid')->executeQuery()
->fetchAllAssociative()
@@ -15,21 +15,13 @@ use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
*/
class PageTree
{
private const FIELDS = [
private const array FIELDS = [
'uid', 'pid', 'sorting', 'doktype', 'hidden', 'starttime', 'endtime', 'fe_group',
'extendToSubpages', 'no_index', 'title', 'nav_title', 'description', 'shortcut',
'shortcut_mode', 'content_from_pid', 'tstamp', 'sys_language_uid', 'l10n_parent', 'l18n_cfg',
];
/**
* @var ConnectionPool
*/
private $connectionPool;
/**
* @var int
*/
private $now;
private readonly int $now;
/**
* @var array<int, array>|null default-language rows by uid
@@ -39,16 +31,15 @@ class PageTree
/**
* @var array<int, int[]> child uids by parent uid, in sorting order
*/
private $children = [];
private array $children = [];
/**
* @var array<int, array<int, array>> overlay rows by default-language uid and language id
*/
private $overlays = [];
private array $overlays = [];
public function __construct(ConnectionPool $connectionPool, Context $context)
public function __construct(private readonly ConnectionPool $connectionPool, Context $context)
{
$this->connectionPool = $connectionPool;
$this->now = (int) $context->getPropertyFromAspect('date', 'timestamp');
}
@@ -26,14 +26,8 @@ class RecordFinder
'news' => 'tx_news_domain_model_news',
];
/**
* @var ConnectionPool
*/
private $connectionPool;
public function __construct(ConnectionPool $connectionPool)
public function __construct(private readonly ConnectionPool $connectionPool)
{
$this->connectionPool = $connectionPool;
}
/**
@@ -18,36 +18,12 @@ use TYPO3\CMS\Core\Database\ConnectionPool;
*/
class RecordSerializer
{
/**
* @var RecordFinder
*/
private $recordFinder;
/**
* @var UrlResolver
*/
private $urlResolver;
/**
* @var RteLinkResolver
*/
private $rteLinkResolver;
/**
* @var ConnectionPool
*/
private $connectionPool;
public function __construct(
RecordFinder $recordFinder,
UrlResolver $urlResolver,
RteLinkResolver $rteLinkResolver,
ConnectionPool $connectionPool
private readonly RecordFinder $recordFinder,
private readonly UrlResolver $urlResolver,
private readonly RteLinkResolver $rteLinkResolver,
private readonly ConnectionPool $connectionPool,
) {
$this->recordFinder = $recordFinder;
$this->urlResolver = $urlResolver;
$this->rteLinkResolver = $rteLinkResolver;
$this->connectionPool = $connectionPool;
}
/**
@@ -62,18 +38,12 @@ class RecordSerializer
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));
}
return match ($type) {
'product' => $this->serializeProducts($rows),
'hotel' => $this->serializeHotels($rows),
'news' => $this->serializeNews($rows),
default => array_values(array_map(fn(array $row): array => $this->serializeDestination($type, $row), $rows)),
};
}
private function serializeProducts(array $rows): array
@@ -137,7 +107,7 @@ class RecordSerializer
foreach ($rows as $uid => $row) {
$code = trim((string) $row['code']);
$codes = array_values(array_unique(array_filter(array_merge([$code], $aliases[$uid] ?? []), 'strlen')));
$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,
@@ -152,9 +122,7 @@ class RecordSerializer
'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]);
})),
'productUids' => array_values(array_filter($products[$uid] ?? [], fn(int $productUid): bool => isset($eligibleProducts[$productUid]))),
'address' => $this->text($row['address']),
'latitude' => $this->text($row['latitude']),
'longitude' => $this->text($row['longitude']),
@@ -192,64 +160,61 @@ class RecordSerializer
'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']),
],
];
}
return match ($type) {
'region' => $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']),
],
],
'country' => $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']),
],
],
'city' => $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']),
],
],
// concept
default => $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
@@ -302,8 +267,13 @@ class RecordSerializer
* @param int[] $uids
* @return array<int, int[]> related uids by $keyField, in $sortField order
*/
private function findMmUids(string $table, string $keyField, string $valueField, array $uids, string $sortField = 'sorting'): array
{
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)
@@ -381,12 +351,11 @@ class RecordSerializer
}
/**
* @param array $infoboxUids
* @return array<int, string|null>
*/
private function findInfoboxTexts(array $infoboxUids): array
{
$infoboxUids = array_values(array_filter(array_map('intval', $infoboxUids)));
$infoboxUids = array_values(array_filter(array_map(intval(...), $infoboxUids)));
if ([] === $infoboxUids) {
return [];
@@ -15,25 +15,13 @@ use TYPO3\CMS\Core\Resource\File;
*/
class RteLinkResolver
{
/**
* @var LinkService
*/
private $linkService;
/**
* @var UrlResolver
*/
private $urlResolver;
public function __construct(LinkService $linkService, UrlResolver $urlResolver)
public function __construct(private readonly LinkService $linkService, private readonly UrlResolver $urlResolver)
{
$this->linkService = $linkService;
$this->urlResolver = $urlResolver;
}
public function resolve(string $html): string
{
if (false === strpos($html, 't3://')) {
if (!str_contains($html, 't3://')) {
return $html;
}
@@ -52,7 +40,7 @@ class RteLinkResolver
{
try {
$data = $this->linkService->resolve($link);
} catch (\Throwable $e) {
} catch (\Throwable) {
return null;
}
@@ -13,42 +13,33 @@ use TYPO3\CMS\Core\Utility\MathUtility;
*/
class UrlResolver
{
private const DOKTYPE_DEFAULT = 1;
private const DOKTYPE_SHORTCUT = 4;
private const SHORTCUT_MODE_NONE = 0;
private const SHORTCUT_MODE_FIRST_SUBPAGE = 1;
private const SHORTCUT_MODE_PARENT_PAGE = 3;
private const MAX_SHORTCUT_DEPTH = 5;
/**
* @var SiteFinder
*/
private $siteFinder;
/**
* @var PageTree
*/
private $pageTree;
private const int DOKTYPE_DEFAULT = 1;
private const int DOKTYPE_SHORTCUT = 4;
private const int SHORTCUT_MODE_NONE = 0;
private const int SHORTCUT_MODE_FIRST_SUBPAGE = 1;
private const int SHORTCUT_MODE_PARENT_PAGE = 3;
private const int MAX_SHORTCUT_DEPTH = 5;
/**
* @var string[]
*/
private $includedSites;
private readonly array $includedSites;
/**
* @var array<string, int> news detail page uid by site identifier
*/
private $newsDetailPages;
private array $newsDetailPages;
/**
* @var array<int, Site|null>
*/
private $sitesByPage = [];
private array $sitesByPage = [];
public function __construct(SiteFinder $siteFinder, PageTree $pageTree, ContentFeedConfiguration $configuration)
{
$this->siteFinder = $siteFinder;
$this->pageTree = $pageTree;
public function __construct(
private readonly SiteFinder $siteFinder,
private readonly PageTree $pageTree,
ContentFeedConfiguration $configuration,
) {
$this->includedSites = $configuration->getSites();
$this->newsDetailPages = $configuration->getNewsDetailPages();
}
@@ -63,7 +54,7 @@ class UrlResolver
foreach ($this->includedSites as $identifier) {
try {
$sites[] = $this->siteFinder->getSiteByIdentifier($identifier);
} catch (SiteNotFoundException $e) {
} catch (SiteNotFoundException) {
// A misspelt identifier must not take the whole feed down; it just contributes nothing.
}
}
@@ -80,7 +71,7 @@ class UrlResolver
try {
$site = $this->siteFinder->getSiteByPageId($pageUid);
$this->sitesByPage[$pageUid] = in_array($site->getIdentifier(), $this->includedSites, true) ? $site : null;
} catch (SiteNotFoundException $e) {
} catch (SiteNotFoundException) {
$this->sitesByPage[$pageUid] = null;
}
}
@@ -92,7 +83,7 @@ class UrlResolver
{
$site = $this->getIncludedSite($pageUid);
if (null === $site) {
if (!$site instanceof Site) {
return null;
}
@@ -100,7 +91,7 @@ class UrlResolver
$language = $site->getLanguageById($languageId);
return (string) $site->getRouter()->generateUri($pageUid, ['_language' => $language] + $parameters);
} catch (\Throwable $e) {
} catch (\Throwable) {
// Unroutable pages (missing slug, disabled language) are left out rather than failing the feed.
return null;
}
@@ -161,7 +152,7 @@ class UrlResolver
$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);
$url = $site instanceof Site ? $this->getPageUrl($target) : null;
return [
'url' => $url,
@@ -179,7 +170,7 @@ class UrlResolver
{
$site = $this->getIncludedSite($storagePid);
if (null === $site) {
if (!$site instanceof Site) {
return null;
}
@@ -2,31 +2,7 @@
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 League\Period\Period;
use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\MyEP\ApiClient as MyEpClient;
use EP\EpProducts\MyEP\ApiException;
@@ -37,11 +13,8 @@ use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class AjaxCalendarController extends ActionController
{
protected MyEpClient $myEpClient;
public function __construct(MyEpClient $myEpClient)
public function __construct(private readonly MyEpClient $myEpClient)
{
$this->myEpClient = $myEpClient;
}
public function priceCalendarAction(Hotel $hotel, int $months = 12): ResponseInterface
@@ -116,7 +89,7 @@ class AjaxCalendarController extends ActionController
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);
$period = new Period($period->getStartDate(), $dataEnd);
}
}
} catch (ApiException $e) {
@@ -139,9 +112,100 @@ class AjaxCalendarController extends ActionController
return $this->htmlResponse();
}
/**
* Availability calendar (Belegungskalender) of the product detail sidebar, for products
* without the price calendar, limited to the months without prices (winter). TYPO3 10
* fetched it month by month from bpn-connect; MyE&P returns the whole snapshot at once,
* so every month is rendered and the month select only switches between them.
*/
public function availabilityAction(Hotel $hotel, int $months = 12): ResponseInterface
{
$hotelCode = trim($hotel->getCode() ?? '');
$this->tagPageCacheForHotel($hotelCode);
$period = DateUtility::getDateRange(null, null, $months)['period'];
$today = new \DateTimeImmutable('today');
$statusByDate = [];
$error = false;
if ('' === $hotelCode) {
$error = true;
$this->disablePageCache();
} else {
try {
foreach ($this->myEpClient->getContingentAvailability($hotelCode) as $item) {
if (!is_array($item) || !isset($item['date'])) {
continue;
}
$statusByDate[$item['date']] = $this->normalizeAvailabilityStatus((string)($item['status'] ?? 'OK'));
}
} catch (ApiException $e) {
$error = true;
$this->disablePageCache();
}
}
if ([] !== $statusByDate) {
$lastDataDate = new \DateTimeImmutable(max(array_keys($statusByDate)));
$dataEnd = $lastDataDate->modify('first day of next month')->setTime(0, 0, 0);
if ($dataEnd > $period->getEndDate()) {
$period = new Period($period->getStartDate(), $dataEnd);
}
}
// Days without a row are available, as on TYPO3 10. A change of the occupancy level
// from the previous day is drawn as a diagonal transition (arrival/departure day).
$classByDate = [];
$previousStatus = $statusByDate[$period->getStartDate()->modify('-1 day')->format('Y-m-d')] ?? 'OK';
for ($day = \DateTimeImmutable::createFromInterface($period->getStartDate()); $day < $period->getEndDate(); $day = $day->modify('+1 day')) {
$dateKey = $day->format('Y-m-d');
$status = $statusByDate[$dateKey] ?? 'OK';
if ($day < $today) {
$classByDate[$dateKey] = 'text-zinc-400';
} elseif ($previousStatus !== $status) {
$classByDate[$dateKey] = 'text-white availability-' . $this->getAvailabilitySuffix($previousStatus) . '-to-' . $this->getAvailabilitySuffix($status);
} else {
$classByDate[$dateKey] = 'text-white availability-' . $this->getAvailabilitySuffix($status);
}
$previousStatus = $status;
}
// Only summer months have prices in the price config, and those are covered by the
// price calendar, so they are left out here. If the price config is unavailable, all
// months are shown, but not cached.
$priceMonths = [];
if (!$error) {
try {
foreach ($this->myEpClient->getPriceConfigData($hotelCode) as $item) {
if (!is_array($item) || !isset($item['date'])) {
continue;
}
if (null !== ($item['pricePerNight'] ?? null) || null !== ($item['defaultPricePerNight'] ?? null)) {
$priceMonths[substr((string)$item['date'], 0, 7)] = true;
}
}
} catch (ApiException $e) {
$this->disablePageCache();
}
}
$calendarMonths = array_values(array_filter(
$this->buildCalendarMonths($period),
fn (array $month): bool => !isset($priceMonths[$month['date']->format('Y-m')])
));
$this->view->assignMultiple([
'classByDate' => $classByDate,
'calendarMonths' => $calendarMonths,
'error' => $error,
]);
return $this->htmlResponse();
}
public function priceTableAction(Hotel $hotel, ?string $year = null): ResponseInterface
{
$year = $year ?? (new \DateTimeImmutable())->format('Y');
$year ??= new \DateTimeImmutable()->format('Y');
$hotelCode = trim($hotel->getCode() ?? '');
$this->tagPageCacheForHotel($hotelCode);
@@ -194,6 +258,25 @@ class AjaxCalendarController extends ActionController
$this->request->getAttribute('frontend.cache.instruction')?->disableCache('Price calendar data could not be retrieved');
}
private function normalizeAvailabilityStatus(string $status): string
{
$status = strtoupper(trim($status));
if ('ONREQUEST' === $status) {
return 'ON_REQUEST';
}
return in_array($status, ['OK', 'ON_REQUEST', 'BLOCKED'], true) ? $status : 'OK';
}
private function getAvailabilitySuffix(string $status): string
{
return match ($status) {
'BLOCKED' => 'none',
'ON_REQUEST' => 'partial',
default => 'full',
};
}
private function buildTooltip(string $status, array $item): string
{
if ($status === 'blocked') {
@@ -237,7 +320,7 @@ class AjaxCalendarController extends ActionController
/**
* @return array<int, array<string, mixed>>
*/
private function buildCalendarMonths(\League\Period\Period $period): array
private function buildCalendarMonths(Period $period): array
{
$months = [];
$current = new \DateTimeImmutable($period->getStartDate()->format('Y-m-01 00:00:00'));
@@ -2,30 +2,6 @@
namespace EP\EpProducts\Controller;
/***************************************************************
*
* Copyright notice
*
* (c) 2021 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 Psr\Http\Message\ResponseInterface;
use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\SearchResultService;
@@ -38,23 +14,8 @@ class AjaxFilterPanelController extends ActionController
use RequestArgumentTypeConversionTrait;
/**
* @var FilterService
*/
protected $filterService;
/**
* @var SearchResultService
*/
private $searchResultService;
/**
* @param FilterService $filterService
*/
public function __construct(FilterService $filterService, SearchResultService $searchResultService)
public function __construct(private FilterService $filterService, private SearchResultService $searchResultService)
{
$this->filterService = $filterService;
$this->searchResultService = $searchResultService;
}
public function initializeAction(): void
@@ -63,9 +24,6 @@ class AjaxFilterPanelController extends ActionController
$this->processFilterSettingsArgument($forcedConceptUids);
}
/**
* @param array $filterSettings
*/
public function indexAction(array $filterSettings): ResponseInterface
{
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids'] ?? '');
@@ -2,30 +2,6 @@
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 Psr\Http\Message\ResponseInterface;
use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\SearchResultService;
@@ -39,35 +15,16 @@ class AjaxSearchController 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)
public function __construct(private FilterService $filterService, private SearchResultService $searchResultService)
{
$this->filterService = $filterService;
$this->searchResultService = $searchResultService;
}
public function initializeSearchresultAction()
public function initializeSearchresultAction(): void
{
$forcedConceptUids = GeneralUtility::intExplode(',', $this->settings['forcedConceptUids'] ?? '', true);
$this->processFilterSettingsArgument($forcedConceptUids);
}
/**
* @param array $filterSettings
*/
public function searchresultAction(array $filterSettings): ResponseInterface
{
$dateSelected = false;
@@ -2,30 +2,6 @@
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 Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Http\ForwardResponse;
use EP\EpProducts\Domain\Model\Infobox;
@@ -48,61 +24,17 @@ class AjaxTableController extends ActionController
use RequestArgumentTypeConversionTrait;
/**
* @var ProductRepository
*/
protected $productRepository;
/**
* @var InfoboxRepository
*/
protected $infoboxRepository;
/**
* @var FilterService
*/
protected $filterService;
/**
* @var DateService
*/
protected $dateService;
/**
* @var ResellerDataService
*/
protected $resellerDataService;
/**
* @var MyEpUrlResolverService
*/
protected $myEpUrlResolverService;
/**
* @param ProductRepository $productRepository
* @param InfoboxRepository $infoboxRepository
* @param DateService $dateService
* @param FilterService $filterService
* @param ResellerDataService $resellerDataService
* @param MyEpUrlResolverService $myEpUrlResolverService
*/
public function __construct(
ProductRepository $productRepository,
InfoboxRepository $infoboxRepository,
DateService $dateService,
FilterService $filterService,
ResellerDataService $resellerDataService,
MyEpUrlResolverService $myEpUrlResolverService
private ProductRepository $productRepository,
private InfoboxRepository $infoboxRepository,
private DateService $dateService,
private FilterService $filterService,
private ResellerDataService $resellerDataService,
private MyEpUrlResolverService $myEpUrlResolverService,
) {
$this->productRepository = $productRepository;
$this->infoboxRepository = $infoboxRepository;
$this->dateService = $dateService;
$this->filterService = $filterService;
$this->resellerDataService = $resellerDataService;
$this->myEpUrlResolverService = $myEpUrlResolverService;
}
public function initializeDatesAction()
public function initializeDatesAction(): void
{
if ($this->request->hasArgument('dateFrom')) {
$dateFrom = $this->request->getArgument('dateFrom');
@@ -114,12 +46,6 @@ class AjaxTableController extends ActionController
}
}
/**
* @param Product $product
* @param Hotel $hotel
* @param string|null $dateFrom
* @param string|null $dateTo
*/
public function datestableAction(
Product $product,
Hotel $hotel,
@@ -141,7 +67,7 @@ class AjaxTableController extends ActionController
if (1 === count($dates)) {
$date = reset($dates);
return (new ForwardResponse('pricetable'))->withExtensionName('ep_products')->withArguments([
return new ForwardResponse('pricetable')->withExtensionName('ep_products')->withArguments([
'product' => $product,
'hotel' => $hotel,
'date' => $date['dateUid'],
@@ -160,9 +86,7 @@ class AjaxTableController extends ActionController
'nonBookableDateUids' => $nonBookableDateUids,
]);
$bookableDates = array_filter($allDates, function (array $date) {
return $date['available'] > 0;
});
$bookableDates = array_filter($allDates, fn(array $date): bool => $date['available'] > 0);
$includedServices = $this->dateService->groupIncludedServices($bookableDates);
$altProductLink = null;
@@ -211,12 +135,6 @@ class AjaxTableController extends ActionController
return $this->htmlResponse();
}
/**
* @param Product $product
* @param Hotel $hotel
* @param Date $date
* @param bool $singleDate
*/
public function pricetableAction(
Product $product,
Hotel $hotel,
@@ -265,10 +183,6 @@ class AjaxTableController extends ActionController
return $this->htmlResponse();
}
/**
* @param Product $product
* @param Hotel $hotel
*/
public function pricetableHtmlAction(Product $product, Hotel $hotel): ResponseInterface
{
$nonBookableInfo = $this->getNonBookableInfobox($product);
@@ -293,18 +207,13 @@ class AjaxTableController extends ActionController
return $this->htmlResponse();
}
/**
* @param Product $product
*/
public function eventPricetableAction(Product $product): ResponseInterface
{
$roomTypes = GeneralUtility::trimExplode(',', $this->settings['priceTableRoomTypes'] ?? '');
$roomTypesPax = array_map(function ($type) {
return (int) $type;
}, $roomTypes);
$roomTypesLabels = array_map(function ($type) {
$roomTypesPax = array_map(fn($type): int => (int) $type, $roomTypes);
$roomTypesLabels = array_map(function ($type): string {
$label = (int) $type . 'er';
if (false !== strpos($type, '+')) {
if (str_contains($type, '+')) {
$label .= '+';
}
return $label;
@@ -334,10 +243,9 @@ class AjaxTableController extends ActionController
}
/**
* @param Product $product
* @return Infobox
*/
protected function getNonBookableInfobox(Product $product)
private function getNonBookableInfobox(Product $product)
{
if (null !== $nonBookableInfo = $product->getNonBookableInfo()) {
return $nonBookableInfo;
@@ -4,14 +4,21 @@ namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
use EP\EpProducts\Domain\Model\Concept;
use EP\EpProducts\Domain\Model\Product;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class AjaxTeaserPopupController extends ActionController
{
public function conceptAction(Concept $concept, string $targetUrl): ResponseInterface
public function conceptAction(Concept $concept, string $targetUrl, ?Product $product = null): ResponseInterface
{
// A product can have its own text; title, image and button stay those of the concept
$popupText = (string)$product?->getPopupText();
if ('' === trim(strip_tags($popupText))) {
$popupText = $concept->getPopupText();
}
$this->view->assignMultiple([
'concept' => $concept,
'popupText' => $popupText,
'targetUrl' => $targetUrl,
]);
return $this->htmlResponse();
@@ -2,33 +2,8 @@
namespace EP\EpProducts\Controller;
/***************************************************************
*
* Copyright notice
*
* (c) 2018 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 Psr\Http\Message\ResponseInterface;
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;
@@ -36,36 +11,10 @@ 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 __construct(
private readonly ProductRepository $productRepository,
private readonly SearchResultService $searchResultService,
) {
}
public function initializeAction(): void
@@ -77,8 +26,6 @@ class AjaxWatchlistController extends ActionController
}
/**
* @param array $watchlistUids
* @return string
* @throws \Doctrine\DBAL\DBALException
*/
public function listAction(array $watchlistUids = []): ResponseInterface
@@ -0,0 +1,61 @@
<?php
namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
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
{
public function __construct(
private readonly CityRepository $cityRepository,
private readonly ProductDataService $productDataService,
) {
}
/**
* @param City $city
*/
public function detailAction(?City $city = null): ResponseInterface
{
if (!$city instanceof 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,
]);
return $this->htmlResponse();
}
public function teasergroupAction(): ResponseInterface
{
$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->request->getAttribute('currentContentObject')->data;
$this->view->assign('data', $data);
$this->view->assign('teasers', $cities);
return $this->htmlResponse();
}
}
@@ -0,0 +1,62 @@
<?php
namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
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
{
public function __construct(
private readonly ConceptRepository $conceptRepository,
private readonly ProductDataService $productDataService,
private readonly PageRepository $pageRepository,
) {
}
/**
* @param Concept $concept
*/
public function detailAction(?Concept $concept = null): ResponseInterface
{
if (!$concept instanceof Concept) {
$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->request->getAttribute('currentContentObject')->data;
$this->view->assignMultiple([
'teasers' => $teasers,
'concept' => $concept,
'filterSettings' => $filterSettings,
'pages' => $pages,
'data' => $data,
]);
return $this->htmlResponse();
}
public function teasergroupAction(): ResponseInterface
{
$concepts = $this->conceptRepository->findAll();
$this->view->assign('teasers', $concepts);
return $this->htmlResponse();
}
}
@@ -0,0 +1,42 @@
<?php
namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
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
{
public function __construct(
private readonly CountryRepository $countryRepository,
private readonly ProductDataService $productDataService,
) {
}
/**
* @param Country $country
*/
public function detailAction(?Country $country = null): ResponseInterface
{
if (!$country instanceof Country) {
$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);
return $this->htmlResponse();
}
}
@@ -0,0 +1,24 @@
<?php
namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
use EP\EpProducts\Domain\Repository\FaqRepository;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class FaqController extends ActionController
{
public function __construct(private readonly FaqRepository $faqRepository)
{
}
public function detailAction(): ResponseInterface
{
$faqUids = ($this->settings['faqUids'] ?? '');
$faqs = $this->faqRepository->findByUids($faqUids);
$this->view->assign('faqs', $faqs);
return $this->htmlResponse();
}
}
@@ -0,0 +1,101 @@
<?php
namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
use EP\EpProducts\Domain\Model\Product;
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
{
public function __construct(
private readonly HotelDataService $hotelDataService,
private readonly HotelRepository $hotelRepository,
private readonly ProductRepository $productRepository,
private readonly ProductDataService $productDataService,
) {
}
/**
* @param Hotel $hotel
*/
public function detailAction(?Hotel $hotel = null): ResponseInterface
{
if ($hotel instanceof Hotel) {
$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);
return $this->htmlResponse();
}
public function detailEventAction(): ResponseInterface
{
$productUid = ($this->settings['productUid'] ?? '');
/** @var Product $product */
$product = $this->productRepository->findByIdentifier($productUid);
$hotelUid = ($this->settings['hotelUid'] ?? '');
/** @var Hotel $hotel */
$hotel = $this->hotelRepository->findByIdentifier($hotelUid);
$this->view->assignMultiple([
'product' => $product,
'hotel' => $hotel,
]);
return $this->htmlResponse();
}
public function teasergroupAction(): ResponseInterface
{
$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->request->getAttribute('currentContentObject')->data;
$this->view->assign('data', $data);
$this->view->assign('bookable', $bookable);
$this->view->assign('nonBookable', $nonBookable);
return $this->htmlResponse();
}
public function dateIndependentTeasergroupAction(): ResponseInterface
{
$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->request->getAttribute('currentContentObject')->data;
$this->view->assign('data', $data);
$this->view->assign('teasers', $hotels);
return $this->htmlResponse();
}
}
@@ -0,0 +1,47 @@
<?php
namespace EP\EpProducts\Controller;
use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Service\IcalService;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Http\PropagateResponseException;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class IcalController extends ActionController
{
public function __construct(
private readonly IcalService $icalService,
) {
}
/**
* The feed is sent as a response of its own, past the page cache: a cached page would lose the
* Content-Disposition header, and availability must not be older than the MyE&P snapshot
*/
public function contingentsAction(Hotel $hotel): ResponseInterface
{
$calendar = $this->icalService->createContingentsFeed($hotel);
// An empty calendar would show the portals every day as free; an error keeps their last import
if (null === $calendar) {
throw new PropagateResponseException(
$this->responseFactory->createResponse(503)
->withHeader('Content-Type', 'text/plain; charset=utf-8')
->withHeader('Cache-Control', 'private, no-store')
->withHeader('Retry-After', '900')
->withBody($this->streamFactory->createStream('Availability temporarily unavailable')),
1761730800
);
}
throw new PropagateResponseException(
$this->responseFactory->createResponse()
->withHeader('Content-Type', 'text/calendar;charset=UTF-8')
->withHeader('Content-Disposition', sprintf('attachment; filename=%s.ical', $hotel->getPathSegment()))
->withHeader('Cache-Control', 'private, no-store')
->withBody($this->streamFactory->createStream($calendar)),
1761730801
);
}
}
@@ -0,0 +1,85 @@
<?php
namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Model\Product;
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
{
public function __construct(
private readonly HotelRepository $hotelRepository,
private readonly ProductRepository $productRepository,
) {
}
public function generalAction(): ResponseInterface
{
$markerData = [];
$selectedHotels = ($this->settings['hotelUids'] ?? '');
$hotelUids = GeneralUtility::trimExplode(',', $selectedHotels);
$hotels = $this->hotelRepository->getListByUids($hotelUids);
if ($hotels) {
foreach ($hotels as $hotel)
{
/** @var 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 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->request->getAttribute('currentContentObject')->data;
$this->view->assign('data', $data);
$this->view->assign('markerData', json_encode($markerData, JSON_THROW_ON_ERROR | JSON_HEX_APOS));
return $this->htmlResponse();
}
/**
* @param int $pageUid
* @return string
*/
private function getUriForPageUid($pageUid)
{
/** @var ContentObjectRenderer $contentObject */
$contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
return $contentObject->typoLink_URL(
[
'parameter' => $pageUid,
]
);
}
}
@@ -0,0 +1,50 @@
<?php
namespace EP\EpProducts\Controller;
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
{
public function __construct(private readonly ApiClient $apiClient)
{
}
public function subscriptionFormAction(
?NewsletterSubscriptionRequest $subscriptionRequest = null,
): ResponseInterface {
$subscriptionRequest ??= new NewsletterSubscriptionRequest();
try {
$lists = $this->apiClient->getNewsletterLists();
} catch (ApiException) {
$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();
}
}
@@ -0,0 +1,18 @@
<?php
namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class PricetableController extends ActionController
{
public function eventAction(): ResponseInterface
{
$productUid = ($this->settings['productUid'] ?? '');
$this->view->assign('productUid', $productUid);
return $this->htmlResponse();
}
}
@@ -2,30 +2,6 @@
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 TYPO3\CMS\Extbase\Http\ForwardResponse;
use Psr\Http\Message\ResponseInterface;
use EP\EpProducts\Domain\Model\Hotel;
@@ -44,74 +20,16 @@ use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class ProductController extends ActionController
{
/**
* @var ProductRepository
*/
protected $productRepository;
/**
* @var HotelRepository
*/
protected $hotelRepository;
/**
* @var FilterService
*/
protected $filterService;
/**
* @var ProductDataService
*/
protected $productDataService;
/**
* @var DateService
*/
protected $dateService;
/**
* @var HotelDataService
*/
protected $hotelDataService;
/**
* @var InfoboxRepository
*/
private $infoboxRepository;
/**
* @var PickupOverviewService
*/
private $pickupOverviewService;
/**
* @param ProductRepository $productRepository
* @param ProductDataService $productDataService
* @param HotelRepository $hotelRepository
* @param HotelDataService $hotelDataService
* @param DateService $dateService
* @param FilterService $filterService
* @param InfoboxRepository $infoboxRepository
* @param PickupOverviewService $pickupOverviewService
*/
public function __construct(
ProductRepository $productRepository,
ProductDataService $productDataService,
HotelRepository $hotelRepository,
HotelDataService $hotelDataService,
DateService $dateService,
FilterService $filterService,
InfoboxRepository $infoboxRepository,
PickupOverviewService $pickupOverviewService
private readonly ProductRepository $productRepository,
private readonly ProductDataService $productDataService,
private readonly HotelRepository $hotelRepository,
private readonly HotelDataService $hotelDataService,
private readonly DateService $dateService,
private readonly FilterService $filterService,
private readonly InfoboxRepository $infoboxRepository,
private readonly PickupOverviewService $pickupOverviewService,
) {
$this->productRepository = $productRepository;
$this->productDataService = $productDataService;
$this->hotelRepository = $hotelRepository;
$this->hotelDataService = $hotelDataService;
$this->dateService = $dateService;
$this->filterService = $filterService;
$this->infoboxRepository = $infoboxRepository;
$this->pickupOverviewService = $pickupOverviewService;
}
/**
@@ -129,7 +47,7 @@ class ProductController extends ActionController
$dateTo = null
): ResponseInterface {
// Get product to display from flexform settings if not provided via url
if ($product === null) {
if (!$product instanceof Product) {
$productUid = ($this->settings['productUid'] ?? null);
$product = $productUid ? $this->productRepository->findByIdentifier($productUid) : null;
}
@@ -155,7 +73,7 @@ class ProductController extends ActionController
$arguments = [
'product' => $product,
];
return (new ForwardResponse('hotelList'))->withArguments($arguments);
return new ForwardResponse('hotelList')->withArguments($arguments);
}
// Use first hotel which should be the only one at this point
if ($product->getHotels()->count() > 0) {
@@ -173,10 +91,6 @@ class ProductController extends ActionController
$isProductWithGroupInquiryForm = ($concept !== null) && \in_array($concept->getUid(), $groupInquiryFormConceptUids);
$nonBookableInfo = $product->getNonBookableInfo();
// Assemble backlink
$backlinkUrl = $this->request->getQueryParams()['ref'] ?? null;
$backLinkLabel = strpos((string)$backlinkUrl, 'suche') !== -1 ? 'zurück zum Suchergebnis' : 'zurück';
$this->view->assignMultiple([
'hotel' => $hotel,
'product' => $product,
@@ -188,8 +102,6 @@ class ProductController extends ActionController
'currentPageUid' => $this->request->getAttribute('frontend.page.information')->getId(),
'dateFrom' => $dateFrom,
'dateTo' => $dateTo,
'referringUrl' => $backlinkUrl,
'backlinkLabel' => $backLinkLabel,
'nonBookableInfo' => $nonBookableInfo,
'pickupsOverview' => $this->pickupOverviewService->getOverview($product, $hotel),
]);
@@ -213,9 +125,6 @@ class ProductController extends ActionController
return $this->htmlResponse();
}
/**
* @param Product $product
*/
public function hotelListAction(Product $product): ResponseInterface
{
$hotels = $this->hotelDataService->getList($product);
@@ -0,0 +1,57 @@
<?php
namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
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
{
public function __construct(
private readonly RegionRepository $regionRepository,
private readonly ProductDataService $productDataService,
) {
}
/**
* @param Region $region
*/
public function detailAction(?Region $region = null): ResponseInterface
{
if (!$region instanceof Region) {
$regionUid = ($this->settings['regionUid'] ?? '');
$region = $this->regionRepository->findByIdentifier($regionUid);
}
$additionalRegionUids = GeneralUtility::trimExplode(',', ($this->settings['additionalRegionUids'] ?? ''), true);
$additionalRegionUids = array_map(fn($uid): int => (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);
return $this->htmlResponse();
}
public function teasergroupAction(): ResponseInterface
{
$countryUid = (int)($this->settings['countryUid'] ?? '');
$season = ($this->settings['season'] ?? '');
$regionsPid = (int)($this->settings['regionsStoragePid'] ?? '');
$regions = $this->regionRepository->getTeasersByCountryAndSeason($countryUid, $season, $regionsPid);
$data = $this->request->getAttribute('currentContentObject')->data;
$this->view->assign('data', $data);
$this->view->assign('teasers', $regions);
return $this->htmlResponse();
}
}
@@ -2,30 +2,6 @@
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 Psr\Http\Message\ResponseInterface;
use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\FilterSettingsEncoder;
@@ -46,26 +22,11 @@ class SearchController extends ActionController
*/
public const SEARCHBAR_PARAMETERS = ['dateFrom', 'dateTo', 'pax', 'destinationType', 'destinationUid', 'priceRange'];
/**
* @var FilterService
*/
protected $filterService;
/**
* @var SearchResultService
*/
private $searchResultService;
/**
* @param FilterService $filterService
*/
public function __construct(FilterService $filterService, SearchResultService $searchResultService)
public function __construct(private FilterService $filterService, private SearchResultService $searchResultService)
{
$this->filterService = $filterService;
$this->searchResultService = $searchResultService;
}
public function initializeSearchresultAction()
public function initializeSearchresultAction(): void
{
// Process encoded filtersettings from static search result.
// Explicit filter settings win: they carry the visitor's current selection,
@@ -81,7 +42,7 @@ class SearchController extends ActionController
// The fields of the search bar, which sends them here by GET
$searchbarParameters = array_filter(
array_intersect_key($this->request->getQueryParams(), array_flip(self::SEARCHBAR_PARAMETERS)),
'is_string'
is_string(...)
);
// Anything but a real Y-m-d date is dropped: the URL is easy to edit, and
// DateUtility::formatDateRange() throws on what DateTime cannot read
@@ -100,9 +61,6 @@ class SearchController extends ActionController
$this->processFilterSettingsArgument($forcedConceptUids);
}
/**
* @param array $filterSettings
*/
public function searchresultAction(array $filterSettings): ResponseInterface
{
$dateSelected = false;
@@ -2,31 +2,6 @@
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\Service\FilterService;
use EP\EpProducts\Service\SearchResultService;
use Psr\Http\Message\ResponseInterface;
@@ -0,0 +1,63 @@
<?php
namespace EP\EpProducts\Controller;
use Psr\Http\Message\ResponseInterface;
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
{
public function __construct(private readonly TeammemberRepository $teammemberRepository)
{
}
public function listAction(): ResponseInterface
{
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);
return $this->htmlResponse();
}
/**
* @param Teammember $member
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
* @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException
*/
public function showAction(?Teammember $member = null): ResponseInterface
{
if (!$member instanceof Teammember) {
return $this->redirect('list', null, null, null, null, 0, 302);
}
$this->view->assign('member', $member);
$this->view->assign('data', $this->request->getAttribute('currentContentObject')->data);
$this->view->assign('header', $this->coalesceHeader($member));
return $this->htmlResponse();
}
/**
* @return string
*/
public function coalesceHeader(Teammember $member)
{
$data = $this->request->getAttribute('currentContentObject')->data;
if (isset($data['header']) && $data['header'] !== '') {
return $data['header'];
}
$firstCategory = current($member->getCategories()->toArray());
if ($firstCategory !== null && $firstCategory !== false) {
return $firstCategory->getTitle();
}
return '';
}
}
@@ -14,22 +14,12 @@ use TYPO3\CMS\Frontend\Controller\ErrorController;
class TravelinfoController extends ActionController
{
private const SELECTION_ID_BOARD = 71;
private const int SELECTION_ID_BOARD = 71;
/**
* @var ApiClient
*/
private $apiClient;
/**
* @var TravelinfoRepository
*/
private $travelinfoRepository;
public function __construct(ApiClient $apiClient, TravelinfoRepository $travelinfoRepository)
{
$this->apiClient = $apiClient;
$this->travelinfoRepository = $travelinfoRepository;
public function __construct(
private readonly ApiClient $apiClient,
private readonly TravelinfoRepository $travelinfoRepository,
) {
}
public function travelCodeAction(string $travelCode): ResponseInterface
@@ -55,7 +45,7 @@ class TravelinfoController extends ActionController
public function travelInfoAction(?Travelinfo $travelInfo = null): ResponseInterface
{
// the page itself (/reiseinformationen/) without a travel info or code
if (null === $travelInfo) {
if (!$travelInfo instanceof Travelinfo) {
$this->notFound();
}
@@ -74,26 +64,24 @@ class TravelinfoController extends ActionController
throw new ImmediateResponseException($response, 1731071856);
}
private function handleRequest(string $travelCode, Travelinfo $travelInfo)
private function handleRequest(string $travelCode, Travelinfo $travelInfo): void
{
// fetch travel data via API call
try {
$travelData = $this->apiClient->getTravel($travelCode);
} catch (ApiException $e) {
} catch (ApiException) {
$travelData = [];
}
try {
$pickupsPlanning = $this->apiClient->getPickupsPlanning($travelCode);
} catch (ApiException $e) {
} catch (ApiException) {
$pickupsPlanning = [];
}
// extract courses from additional services
$courses = array_filter($travelData['additionalServices'] ?? [], function ($service) {
return 'KUR' === $service['subType'];
});
$courses = array_filter($travelData['additionalServices'] ?? [], fn($service): bool => 'KUR' === $service['subType']);
// extract board services from included services
$includedServicesBoard = $travelData['selectionGroups'][self::SELECTION_ID_BOARD] ?? [];
@@ -101,9 +89,7 @@ class TravelinfoController extends ActionController
// extract bus departure time if applicable
$departureDayTime = null;
$service = array_filter($travelData['transportationServices'] ?? [], function ($service) {
return 'BUS' === $service['subType'] && 'RUECK' === $service['direction'];
});
$service = array_filter($travelData['transportationServices'] ?? [], fn($service): bool => 'BUS' === $service['subType'] && 'RUECK' === $service['direction']);
if ($serviceDetail = reset($service)) {
$departureDayTime = $serviceDetail['dayTime'];
}
@@ -2,30 +2,6 @@
namespace EP\EpProducts\Controller;
/***************************************************************
*
* Copyright notice
*
* (c) 2018 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 Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
@@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Dashboard;
use EP\EpProducts\MyEP\ApiClient;
use EP\EpProducts\Service\ApiImportSource;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\View\BackendViewFactory;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Dashboard\Widgets\RequestAwareWidgetInterface;
use TYPO3\CMS\Dashboard\Widgets\WidgetConfigurationInterface;
use TYPO3\CMS\Dashboard\Widgets\WidgetInterface;
/**
* When the imports last ran and what data they brought, replacing the TYPO3 10 system information entry
* "Letzer Produktdaten-Import"
*/
final class ImportStatusWidget implements WidgetInterface, RequestAwareWidgetInterface
{
/**
* Command => label; the scheduler tasks are found by the command they run, not by uid
*/
private const array COMMANDS = [
'ep:import-dates' => 'Reisedaten (MyE&P)',
'ep:import-snowreport' => 'Schneehöhen',
'ep:groups-swiss' => 'groups.swiss',
];
/**
* A task whose next run is this far in the past means scheduler:run is not called any more
*/
private const int OVERDUE_SECONDS = 3600;
private ServerRequestInterface $request;
public function __construct(
private readonly WidgetConfigurationInterface $configuration,
private readonly BackendViewFactory $backendViewFactory,
private readonly ConnectionPool $connectionPool,
private readonly ApiImportSource $importSource,
private readonly ApiClient $apiClient,
private readonly array $options = [],
) {
}
public function setRequest(ServerRequestInterface $request): void
{
$this->request = $request;
}
public function renderWidgetContent(): string
{
$tasks = $this->findTasks();
$rows = [];
foreach (self::COMMANDS as $command => $label) {
$rows[$command] = ['label' => $label, 'dataAt' => null, 'task' => $tasks[$command] ?? null];
}
$rows['ep:import-dates']['dataAt'] = $this->importSource->getImportedTimestamp()?->getTimestamp();
$rows['ep:import-snowreport']['dataAt'] = $this->findLastSnowreportUpdate();
$myEpLastUpdate = null;
$myEpUnreachable = false;
try {
$myEpLastUpdate = $this->apiClient->getLastUpdateAt()?->getTimestamp();
} catch (\Throwable) {
$myEpUnreachable = true;
}
$view = $this->backendViewFactory->create($this->request, ['typo3/cms-dashboard', 'ep/ep-products']);
$view->assignMultiple([
'rows' => $rows,
'myEpLastUpdate' => $myEpLastUpdate,
'myEpUnreachable' => $myEpUnreachable,
'importPending' => null !== $myEpLastUpdate && $myEpLastUpdate > (int)$rows['ep:import-dates']['dataAt'],
'configuration' => $this->configuration,
]);
return $view->render('Widget/ImportStatusWidget');
}
public function getOptions(): array
{
return $this->options;
}
/**
* @return array<string, array{uid: int, disabled: bool, lastRun: int, nextRun: int, overdue: bool, failure: ?string}>
*/
private function findTasks(): array
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_scheduler_task');
$result = $queryBuilder
->select('uid', 'disable', 'lastexecution_time', 'lastexecution_failure', 'nextexecution', 'serialized_task_object')
->from('tx_scheduler_task')
->where($queryBuilder->expr()->eq('deleted', 0))
->orderBy('uid')
->executeQuery();
$tasks = [];
while ($row = $result->fetchAssociative()) {
foreach (array_keys(self::COMMANDS) as $command) {
// The command identifier appears in the serialized ExecuteSchedulableCommandTask exactly as
// serialize() writes the string; matching it avoids unserializing task objects
if (!isset($tasks[$command]) && str_contains((string)$row['serialized_task_object'], serialize($command))) {
$tasks[$command] = [
'uid' => (int)$row['uid'],
'disabled' => (bool)$row['disable'],
'lastRun' => (int)$row['lastexecution_time'],
'nextRun' => (int)$row['nextexecution'],
'overdue' => !$row['disable'] && 0 < (int)$row['nextexecution']
&& (int)$row['nextexecution'] < time() - self::OVERDUE_SECONDS,
'failure' => $this->failureMessage((string)$row['lastexecution_failure']),
];
}
}
}
return $tasks;
}
private function failureMessage(string $failure): ?string
{
if ('' === $failure) {
return null;
}
// Written by the scheduler as serialize(['code' => …, 'message' => …, …])
$data = @unserialize($failure, ['allowed_classes' => false]);
return is_array($data) && '' !== (string)($data['message'] ?? '') ? (string)$data['message'] : 'Unbekannter Fehler';
}
private function findLastSnowreportUpdate(): ?int
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_snowreport');
$queryBuilder->getRestrictions()->removeAll();
$tstamp = $queryBuilder
->selectLiteral('MAX(tstamp)')
->from('tx_epproducts_domain_model_snowreport')
->executeQuery()
->fetchOne();
return $tstamp ? (int)$tstamp : null;
}
}
@@ -6,30 +6,6 @@ use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
/***************************************************************
*
* 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!
***************************************************************/
class AbstractDestination extends AbstractEntity
{
@@ -139,7 +115,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $detailPage
*/
public function setDetailPage($detailPage)
public function setDetailPage($detailPage): void
{
$this->detailPage = $detailPage;
}
@@ -155,7 +131,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $season
*/
public function setSeason($season)
public function setSeason($season): void
{
$this->season = $season;
}
@@ -181,7 +157,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param float $latitude
*/
public function setLatitude($latitude)
public function setLatitude($latitude): void
{
$this->latitude = $latitude;
}
@@ -197,7 +173,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param float $longitude
*/
public function setLongitude($longitude)
public function setLongitude($longitude): void
{
$this->longitude = $longitude;
}
@@ -213,7 +189,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $name
*/
public function setName($name)
public function setName($name): void
{
$this->name = $name;
}
@@ -229,7 +205,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $nameInternal
*/
public function setNameInternal($nameInternal)
public function setNameInternal($nameInternal): void
{
$this->nameInternal = $nameInternal;
}
@@ -245,7 +221,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $keywords
*/
public function setKeywords($keywords)
public function setKeywords($keywords): void
{
$this->keywords = $keywords;
}
@@ -261,7 +237,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $headline
*/
public function setHeadline($headline)
public function setHeadline($headline): void
{
$this->headline = $headline;
}
@@ -277,7 +253,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $teaser
*/
public function setTeaser($teaser)
public function setTeaser($teaser): void
{
$this->teaser = $teaser;
}
@@ -293,7 +269,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $headerTitle
*/
public function setHeaderTitle($headerTitle)
public function setHeaderTitle($headerTitle): void
{
$this->headerTitle = $headerTitle;
}
@@ -309,7 +285,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $headerSubtitle
*/
public function setHeaderSubtitle($headerSubtitle)
public function setHeaderSubtitle($headerSubtitle): void
{
$this->headerSubtitle = $headerSubtitle;
}
@@ -325,7 +301,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $description
*/
public function setDescription($description)
public function setDescription($description): void
{
$this->description = $description;
}
@@ -341,7 +317,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param string $descriptionExtended
*/
public function setDescriptionExtended($descriptionExtended)
public function setDescriptionExtended($descriptionExtended): void
{
$this->descriptionExtended = $descriptionExtended;
}
@@ -357,7 +333,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param ObjectStorage<FileReference> $images
*/
public function setImages(ObjectStorage $images)
public function setImages(ObjectStorage $images): void
{
$this->images = $images;
}
@@ -385,7 +361,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param ObjectStorage $teaserImages
*/
public function setTeaserImages($teaserImages)
public function setTeaserImages($teaserImages): void
{
$this->teaserImages = $teaserImages;
}
@@ -415,7 +391,7 @@ class AbstractDestination extends AbstractEntity
/**
* @param ObjectStorage $headerImages
*/
public function setHeaderImages($headerImages)
public function setHeaderImages($headerImages): void
{
$this->headerImages = $headerImages;
}
@@ -1,33 +1,11 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/***************************************************************
*
* 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!
***************************************************************/
class Badge extends AbstractEntity
{
@@ -57,7 +35,7 @@ class Badge extends AbstractEntity
/**
* @param string $title
*/
public function setTitle($title)
public function setTitle($title): void
{
$this->title = $title;
}
@@ -73,7 +51,7 @@ class Badge extends AbstractEntity
/**
* @param string $subtitle
*/
public function setSubtitle($subtitle)
public function setSubtitle($subtitle): void
{
$this->subtitle = $subtitle;
}
@@ -89,10 +67,9 @@ class Badge extends AbstractEntity
/**
* @param string $link
*/
public function setLink($link)
public function setLink($link): void
{
$this->link = $link;
}
}
@@ -4,30 +4,6 @@ namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/***************************************************************
*
* 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!
***************************************************************/
class City extends AbstractDestination implements TeaserInterface
{
@@ -83,7 +59,7 @@ class City extends AbstractDestination implements TeaserInterface
/**
* @param bool $excludeFromTeasergroups
*/
public function setExcludeFromTeasergroups($excludeFromTeasergroups)
public function setExcludeFromTeasergroups($excludeFromTeasergroups): void
{
$this->excludeFromTeasergroups = (bool) $excludeFromTeasergroups;
}
@@ -98,7 +74,7 @@ class City extends AbstractDestination implements TeaserInterface
/**
* @param string $party
*/
public function setParty($party) {
public function setParty($party): void {
$this->party = $party;
}
@@ -112,7 +88,7 @@ class City extends AbstractDestination implements TeaserInterface
/**
* @param string $leisure
*/
public function setLeisure($leisure) {
public function setLeisure($leisure): void {
$this->leisure = $leisure;
}
@@ -127,7 +103,7 @@ class City extends AbstractDestination implements TeaserInterface
$headerImages = $this->headerImages;
if ($headerImages->count() === 0 && null !== $this->getRegion()) {
$headerImages = $this->getRegion()->getHeaderImages();
return $this->getRegion()->getHeaderImages();
}
return $headerImages;
@@ -141,10 +117,7 @@ class City extends AbstractDestination implements TeaserInterface
return $this->country;
}
/**
* @param Country $country
*/
public function setCountry(Country $country)
public function setCountry(Country $country): void
{
$this->country = $country;
}
@@ -157,10 +130,7 @@ class City extends AbstractDestination implements TeaserInterface
return $this->region;
}
/**
* @param Region $region
*/
public function setRegion(Region $region)
public function setRegion(Region $region): void
{
$this->region = $region;
}
@@ -176,7 +146,7 @@ class City extends AbstractDestination implements TeaserInterface
/**
* @param ObjectStorage<Fact> $facts
*/
public function setFacts(ObjectStorage $facts)
public function setFacts(ObjectStorage $facts): void
{
$this->facts = $facts;
}
@@ -192,7 +162,7 @@ class City extends AbstractDestination implements TeaserInterface
/**
* @param Officetip $officetip
*/
public function setOfficetip($officetip)
public function setOfficetip($officetip): void
{
$this->officetip = $officetip;
}
@@ -6,30 +6,6 @@ use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
/***************************************************************
*
* 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!
***************************************************************/
class Concept extends AbstractEntity implements TeaserInterface
{
@@ -146,7 +122,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param int $busProId
*/
public function setBusProId($busProId) {
public function setBusProId($busProId): void {
$this->busProId = $busProId;
}
@@ -161,7 +137,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param string $name
*/
public function setName($name)
public function setName($name): void
{
$this->name = $name;
}
@@ -177,7 +153,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param string $code
*/
public function setCode($code)
public function setCode($code): void
{
$this->code = $code;
}
@@ -193,7 +169,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param string $teaser
*/
public function setTeaser($teaser)
public function setTeaser($teaser): void
{
$this->teaser = $teaser;
}
@@ -203,7 +179,7 @@ class Concept extends AbstractEntity implements TeaserInterface
return $this->popupText;
}
public function setPopupText($popupText)
public function setPopupText($popupText): static
{
$this->popupText = $popupText;
@@ -215,7 +191,7 @@ class Concept extends AbstractEntity implements TeaserInterface
return $this->popupTitle;
}
public function setPopupTitle($popupTitle)
public function setPopupTitle($popupTitle): static
{
$this->popupTitle = $popupTitle;
@@ -227,7 +203,7 @@ class Concept extends AbstractEntity implements TeaserInterface
return $this->popupButtonLabel;
}
public function setPopupButtonLabel($popupButtonLabel)
public function setPopupButtonLabel($popupButtonLabel): static
{
$this->popupButtonLabel = $popupButtonLabel;
@@ -245,7 +221,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param string $headline
*/
public function setHeadline($headline)
public function setHeadline($headline): void
{
$this->headline = $headline;
}
@@ -261,7 +237,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param string $headerTitle
*/
public function setHeaderTitle($headerTitle)
public function setHeaderTitle($headerTitle): void
{
$this->headerTitle = $headerTitle;
}
@@ -277,7 +253,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param string $headerSubtitle
*/
public function setHeaderSubtitle($headerSubtitle)
public function setHeaderSubtitle($headerSubtitle): void
{
$this->headerSubtitle = $headerSubtitle;
}
@@ -293,7 +269,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param string $description
*/
public function setDescription($description)
public function setDescription($description): void
{
$this->description = $description;
}
@@ -309,7 +285,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param string $descriptionExtended
*/
public function setDescriptionExtended($descriptionExtended)
public function setDescriptionExtended($descriptionExtended): void
{
$this->descriptionExtended = $descriptionExtended;
}
@@ -325,7 +301,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param string $detailPage
*/
public function setDetailPage($detailPage)
public function setDetailPage($detailPage): void
{
$this->detailPage = $detailPage;
}
@@ -353,7 +329,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage $teaserImages
*/
public function setTeaserImages($teaserImages)
public function setTeaserImages($teaserImages): void
{
$this->teaserImages = $teaserImages;
}
@@ -381,7 +357,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage $headerImages
*/
public function setHeaderImages($headerImages)
public function setHeaderImages($headerImages): void
{
$this->headerImages = $headerImages;
}
@@ -397,23 +373,17 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage $images
*/
public function setImages($images)
public function setImages($images): void
{
$this->images = $images;
}
/**
* @param Product $product
*/
public function addProduct(Product $product)
public function addProduct(Product $product): void
{
$this->products->attach($product);
}
/**
* @param Product $product
*/
public function removeProduct(Product $product)
public function removeProduct(Product $product): void
{
$this->products->detach($product);
}
@@ -429,7 +399,7 @@ class Concept extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage<Product> $products
*/
public function setProducts(ObjectStorage $products)
public function setProducts(ObjectStorage $products): void
{
$this->products = $products;
}
@@ -4,30 +4,6 @@ namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/***************************************************************
*
* 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!
***************************************************************/
class Country extends AbstractDestination implements TeaserInterface
{
@@ -39,6 +15,7 @@ class Country extends AbstractDestination implements TeaserInterface
/**
* @var string
*/
#[\Override]
protected $descriptionExtended = '';
/**
@@ -72,7 +49,7 @@ class Country extends AbstractDestination implements TeaserInterface
/**
* @param string $code
*/
public function setCode($code) {
public function setCode($code): void {
$this->code = $code;
}
@@ -87,23 +64,17 @@ class Country extends AbstractDestination implements TeaserInterface
/**
* @param string $descriptionExtended
*/
public function setDescriptionExtended($descriptionExtended)
public function setDescriptionExtended($descriptionExtended): void
{
$this->descriptionExtended = $descriptionExtended;
}
/**
* @param Fact $fact
*/
public function addFact(Fact $fact)
public function addFact(Fact $fact): void
{
$this->facts->attach($fact);
}
/**
* @param Fact $factToRemove
*/
public function removeFact(Fact $factToRemove)
public function removeFact(Fact $factToRemove): void
{
$this->facts->detach($factToRemove);
}
@@ -119,7 +90,7 @@ class Country extends AbstractDestination implements TeaserInterface
/**
* @param ObjectStorage<Fact> $facts
*/
public function setFacts(ObjectStorage $facts)
public function setFacts(ObjectStorage $facts): void
{
$this->facts = $facts;
}
@@ -135,7 +106,7 @@ class Country extends AbstractDestination implements TeaserInterface
/**
* @param Officetip $officetip
*/
public function setOfficetip($officetip)
public function setOfficetip($officetip): void
{
$this->officetip = $officetip;
}
@@ -151,7 +122,7 @@ class Country extends AbstractDestination implements TeaserInterface
/**
* @param Snowreport $snowreport
*/
public function setSnowreport($snowreport)
public function setSnowreport($snowreport): void
{
$this->snowreport = $snowreport;
}
@@ -4,30 +4,6 @@ namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/***************************************************************
*
* 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!
***************************************************************/
class Date extends AbstractEntity
{
/**
@@ -190,7 +166,7 @@ class Date extends AbstractEntity
return $this->product;
}
public function setProduct($product)
public function setProduct($product): void
{
$this->product = $product;
}
@@ -200,7 +176,7 @@ class Date extends AbstractEntity
return $this->hotel;
}
public function setHotel($hotel)
public function setHotel($hotel): void
{
$this->hotel = $hotel;
}
@@ -210,7 +186,7 @@ class Date extends AbstractEntity
return $this->country;
}
public function setCountry($country)
public function setCountry($country): void
{
$this->country = $country;
}
@@ -220,7 +196,7 @@ class Date extends AbstractEntity
return $this->region;
}
public function setRegion($region)
public function setRegion($region): void
{
$this->region = $region;
}
@@ -230,7 +206,7 @@ class Date extends AbstractEntity
return $this->city;
}
public function setCity($city)
public function setCity($city): void
{
$this->city = $city;
}
@@ -246,7 +222,7 @@ class Date extends AbstractEntity
/**
* @param int $busProId
*/
public function setBusProId($busProId)
public function setBusProId($busProId): void
{
$this->busProId = $busProId;
}
@@ -262,7 +238,7 @@ class Date extends AbstractEntity
/**
* @param int $hotelBusProId
*/
public function setHotelBusProId($hotelBusProId)
public function setHotelBusProId($hotelBusProId): void
{
$this->hotelBusProId = $hotelBusProId;
}
@@ -278,7 +254,7 @@ class Date extends AbstractEntity
/**
* @param string $code
*/
public function setCode($code)
public function setCode($code): void
{
$this->code = $code;
}
@@ -294,7 +270,7 @@ class Date extends AbstractEntity
/**
* @param bool $daytrip
*/
public function setDaytrip($daytrip)
public function setDaytrip($daytrip): void
{
$this->daytrip = $daytrip;
}
@@ -307,10 +283,7 @@ class Date extends AbstractEntity
return $this->dateStart;
}
/**
* @param \DateTime $dateStart
*/
public function setDateStart(\DateTime $dateStart)
public function setDateStart(\DateTime $dateStart): void
{
$this->dateStart = $dateStart;
}
@@ -323,10 +296,7 @@ class Date extends AbstractEntity
return $this->dateEnd;
}
/**
* @param \DateTime $dateEnd
*/
public function setDateEnd(\DateTime $dateEnd)
public function setDateEnd(\DateTime $dateEnd): void
{
$this->dateEnd = $dateEnd;
}
@@ -342,7 +312,7 @@ class Date extends AbstractEntity
/**
* @param int $nights
*/
public function setNights($nights)
public function setNights($nights): void
{
$this->nights = $nights;
}
@@ -358,7 +328,7 @@ class Date extends AbstractEntity
/**
* @param string $board
*/
public function setBoard($board)
public function setBoard($board): void
{
$this->board = $board;
}
@@ -371,10 +341,7 @@ class Date extends AbstractEntity
return $this->boardCode;
}
/**
* @param string $boardCode
*/
public function setBoardCode(string $boardCode)
public function setBoardCode(string $boardCode): void
{
$this->boardCode = $boardCode;
}
@@ -387,10 +354,7 @@ class Date extends AbstractEntity
return $this->boardBusProId;
}
/**
* @param int $boardBusProId
*/
public function setBoardBusProId(int $boardBusProId)
public function setBoardBusProId(int $boardBusProId): void
{
$this->boardBusProId = $boardBusProId;
}
@@ -406,7 +370,7 @@ class Date extends AbstractEntity
/**
* @param bool $busIncluded
*/
public function setBusIncluded($busIncluded)
public function setBusIncluded($busIncluded): void
{
$this->busIncluded = $busIncluded;
}
@@ -422,7 +386,7 @@ class Date extends AbstractEntity
/**
* @param bool $skipassIncluded
*/
public function setSkipassIncluded($skipassIncluded)
public function setSkipassIncluded($skipassIncluded): void
{
$this->skipassIncluded = $skipassIncluded;
}
@@ -438,7 +402,7 @@ class Date extends AbstractEntity
/**
* @param bool $groupTravel
*/
public function setGroupTravel($groupTravel)
public function setGroupTravel($groupTravel): void
{
$this->groupTravel = $groupTravel;
}
@@ -454,7 +418,7 @@ class Date extends AbstractEntity
/**
* @param bool $busAvailable
*/
public function setBusAvailable($busAvailable)
public function setBusAvailable($busAvailable): void
{
$this->busAvailable = $busAvailable;
}
@@ -470,7 +434,7 @@ class Date extends AbstractEntity
/**
* @param float $busPrice
*/
public function setBusPrice($busPrice)
public function setBusPrice($busPrice): void
{
$this->busPrice = $busPrice;
}
@@ -478,15 +442,12 @@ class Date extends AbstractEntity
/**
* @return array $servicesIncluded
*/
public function getServicesIncluded()
public function getServicesIncluded(): mixed
{
return unserialize($this->servicesIncluded);
}
/**
* @param array $servicesIncluded
*/
public function setServicesIncluded(array $servicesIncluded)
public function setServicesIncluded(array $servicesIncluded): void
{
$this->servicesIncluded = serialize($servicesIncluded);
}
@@ -505,20 +466,17 @@ class Date extends AbstractEntity
return $services;
}
/**
* @param array $servicesGeneral
*/
public function setServicesGeneral(array $servicesGeneral)
public function setServicesGeneral(array $servicesGeneral): void
{
$this->servicesGeneral = serialize($servicesGeneral);
}
public function getCoursesIncluded()
public function getCoursesIncluded(): mixed
{
return unserialize($this->coursesIncluded);
}
public function setCoursesIncluded($coursesIncluded)
public function setCoursesIncluded($coursesIncluded): void
{
$this->coursesIncluded = $coursesIncluded;
}
@@ -528,7 +486,7 @@ class Date extends AbstractEntity
return $this->getServicesGeneral('KUR');
}
public function getGuide()
public function getGuide(): mixed
{
return unserialize($this->guide);
}
@@ -541,15 +499,12 @@ class Date extends AbstractEntity
/**
* @return string
*/
public function getPickups()
public function getPickups(): mixed
{
return unserialize($this->pickups);
}
/**
* @param array $pickups
*/
public function setPickups(array $pickups)
public function setPickups(array $pickups): void
{
$this->pickups = serialize($pickups);
}
@@ -564,7 +519,7 @@ class Date extends AbstractEntity
/**
* @param float $discount
*/
public function setDiscount($discount) {
public function setDiscount($discount): void {
$this->discount = $discount;
}
@@ -579,7 +534,7 @@ class Date extends AbstractEntity
/**
* @param float $minPrice
*/
public function setMinPrice($minPrice)
public function setMinPrice($minPrice): void
{
$this->minPrice = $minPrice;
}
@@ -595,7 +550,7 @@ class Date extends AbstractEntity
/**
* @param float $pseudoPrice
*/
public function setPseudoPrice($pseudoPrice)
public function setPseudoPrice($pseudoPrice): void
{
$this->pseudoPrice = $pseudoPrice;
}
@@ -611,7 +566,7 @@ class Date extends AbstractEntity
/**
* @param int $available
*/
public function setAvailable($available)
public function setAvailable($available): void
{
$this->available = $available;
}
@@ -621,7 +576,7 @@ class Date extends AbstractEntity
return $this->minPax;
}
public function setMinPax($minPax)
public function setMinPax($minPax): static
{
$this->minPax = $minPax;
@@ -633,7 +588,7 @@ class Date extends AbstractEntity
return $this->new;
}
public function setNew(bool $new)
public function setNew(bool $new): void
{
$this->new = $new;
}
@@ -2,31 +2,6 @@
namespace EP\EpProducts\Domain\Model\Dto;
/***************************************************************
*
* 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!
***************************************************************/
class FilterOptions implements \JsonSerializable
{
/**
@@ -44,67 +19,55 @@ class FilterOptions implements \JsonSerializable
/**
* @var array
*/
protected $priceRanges = [];
private $priceRanges = [];
private array $destinations = [];
/**
* @var array
*/
protected $destinations = [];
private $concepts = [];
private array $pax;
/**
* @var array
*/
protected $concepts = [];
private $nights = [];
/**
* @var array
*/
protected $pax = [];
private $hotelTypes = [];
/**
* @var array
*/
protected $nights = [];
private $boardTypes = [];
/**
* @var array
*/
protected $hotelTypes = [];
/**
* @var array
*/
protected $boardTypes = [];
/**
* @var array
*/
protected $roomTypes = [];
private $roomTypes = [];
/**
* @var \DateTime
*/
protected $dateFrom;
private $dateFrom;
/**
* @var \DateTime
*/
protected $dateTo;
private $dateTo;
/**
* @var bool
*/
protected $busAvailable;
private $busAvailable;
/**
* @var int
*/
protected $resultCount;
private ?int $resultCount = null;
/**
* @var string
*/
protected $resultsUri;
private ?string $resultsUri = null;
public function __construct()
{
@@ -125,7 +88,7 @@ class FilterOptions implements \JsonSerializable
/**
* @param array $priceRanges
*/
public function setPriceRanges($priceRanges)
public function setPriceRanges($priceRanges): void
{
$this->priceRanges = $priceRanges;
}
@@ -138,10 +101,7 @@ class FilterOptions implements \JsonSerializable
return $this->destinations;
}
/**
* @param array $destinations
*/
public function setDestinations(array $destinations)
public function setDestinations(array $destinations): void
{
$this->destinations = $destinations;
}
@@ -157,7 +117,7 @@ class FilterOptions implements \JsonSerializable
/**
* @param array $concepts
*/
public function setConcepts($concepts)
public function setConcepts($concepts): void
{
$this->concepts = $concepts;
}
@@ -173,7 +133,7 @@ class FilterOptions implements \JsonSerializable
/**
* @param array $pax
*/
public function setPax($pax)
public function setPax($pax): void
{
$this->pax = $pax;
}
@@ -189,7 +149,7 @@ class FilterOptions implements \JsonSerializable
/**
* @param array $nights
*/
public function setNights($nights)
public function setNights($nights): void
{
$this->nights = $nights;
}
@@ -205,7 +165,7 @@ class FilterOptions implements \JsonSerializable
/**
* @param array $hotelTypes
*/
public function setHotelTypes($hotelTypes)
public function setHotelTypes($hotelTypes): void
{
$this->hotelTypes = $hotelTypes;
}
@@ -221,7 +181,7 @@ class FilterOptions implements \JsonSerializable
/**
* @param array $boardTypes
*/
public function setBoardTypes($boardTypes)
public function setBoardTypes($boardTypes): void
{
$this->boardTypes = $boardTypes;
}
@@ -237,7 +197,7 @@ class FilterOptions implements \JsonSerializable
/**
* @param array $roomTypes
*/
public function setRoomTypes($roomTypes)
public function setRoomTypes($roomTypes): void
{
$this->roomTypes = $roomTypes;
}
@@ -253,7 +213,7 @@ class FilterOptions implements \JsonSerializable
/**
* @param \DateTime $dateFrom
*/
public function setDateFrom($dateFrom)
public function setDateFrom($dateFrom): void
{
$this->dateFrom = $dateFrom;
}
@@ -269,7 +229,7 @@ class FilterOptions implements \JsonSerializable
/**
* @param \DateTime $dateTo
*/
public function setDateTo($dateTo)
public function setDateTo($dateTo): void
{
$this->dateTo = $dateTo;
}
@@ -285,7 +245,7 @@ class FilterOptions implements \JsonSerializable
/**
* @param bool $busAvailable
*/
public function setBusAvailable($busAvailable)
public function setBusAvailable($busAvailable): void
{
$this->busAvailable = $busAvailable;
}
@@ -298,10 +258,7 @@ class FilterOptions implements \JsonSerializable
return $this->resultCount;
}
/**
* @param int $resultCount
*/
public function setResultCount(int $resultCount)
public function setResultCount(int $resultCount): void
{
$this->resultCount = $resultCount;
}
@@ -314,10 +271,7 @@ class FilterOptions implements \JsonSerializable
return $this->resultsUri;
}
/**
* @param string $resultsUri
*/
public function setResultsUri(string $resultsUri)
public function setResultsUri(string $resultsUri): void
{
$this->resultsUri = $resultsUri;
}
@@ -98,9 +98,7 @@ class NewsletterSubscriptionRequest implements JsonSerializable
'firstName' => $this->firstName,
'lastName' => $this->lastName,
'email' => $this->email,
'listIds' => array_map(function($value) {
return (int) $value;
}, $this->listIds),
'listIds' => array_map(fn($value): int => (int) $value, $this->listIds),
];
}
}
@@ -1,33 +1,11 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/***************************************************************
*
* 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!
***************************************************************/
class Fact extends AbstractEntity
{
@@ -57,7 +35,7 @@ class Fact extends AbstractEntity
/**
* @param string $name
*/
public function setName($name)
public function setName($name): void
{
$this->name = $name;
}
@@ -73,7 +51,7 @@ class Fact extends AbstractEntity
/**
* @param string $keywords
*/
public function setKeywords($keywords)
public function setKeywords($keywords): void
{
$this->keywords = $keywords;
}
@@ -89,7 +67,7 @@ class Fact extends AbstractEntity
/**
* @param string $label
*/
public function setLabel($label)
public function setLabel($label): void
{
$this->label = $label;
}
@@ -1,33 +1,11 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/***************************************************************
*
* 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!
***************************************************************/
class Faq extends AbstractEntity
{
@@ -52,7 +30,7 @@ class Faq extends AbstractEntity
/**
* @param string $question
*/
public function setQuestion($question)
public function setQuestion($question): void
{
$this->question = $question;
}
@@ -68,9 +46,9 @@ class Faq extends AbstractEntity
/**
* @param string $answer
*/
public function setAnswer($answer)
public function setAnswer($answer): void
{
$this->answer = $answer;
}
}
}
@@ -4,31 +4,6 @@ namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/***************************************************************
*
* Copyright notice
*
* (c) 2020 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!
***************************************************************/
class GroupsPriceBoard extends AbstractEntity implements \JsonSerializable
{
/**
@@ -46,33 +21,21 @@ class GroupsPriceBoard extends AbstractEntity implements \JsonSerializable
*/
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;
@@ -86,17 +49,11 @@ class GroupsPriceBoard extends AbstractEntity implements \JsonSerializable
return $this->priceChf;
}
/**
* @param float $price
*/
public function setPriceChf(float $price): void
{
$this->priceChf = $price;
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return [
@@ -4,31 +4,6 @@ namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/***************************************************************
*
* Copyright notice
*
* (c) 2020 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!
***************************************************************/
class GroupsPriceConfig extends AbstractEntity implements \JsonSerializable
{
/**
@@ -66,65 +41,41 @@ class GroupsPriceConfig extends AbstractEntity implements \JsonSerializable
*/
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;
@@ -138,9 +89,6 @@ class GroupsPriceConfig extends AbstractEntity implements \JsonSerializable
return $this->priceChf;
}
/**
* @param float $price
*/
public function setPriceChf(float $price): void
{
$this->priceChf = $price;
@@ -154,9 +102,6 @@ class GroupsPriceConfig extends AbstractEntity implements \JsonSerializable
return $this->priceAdditionalPerson;
}
/**
* @param float $priceAdditionalPerson
*/
public function setPriceAdditionalPerson(float $priceAdditionalPerson): void
{
$this->priceAdditionalPerson = $priceAdditionalPerson;
@@ -170,17 +115,11 @@ class GroupsPriceConfig extends AbstractEntity implements \JsonSerializable
return $this->priceAdditionalPersonChf;
}
/**
* @param float $priceAdditionalPerson
*/
public function setPriceAdditionalPersonChf(float $priceAdditionalPerson): void
{
$this->priceAdditionalPersonChf = $priceAdditionalPerson;
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return [
@@ -4,31 +4,6 @@ namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/***************************************************************
*
* Copyright notice
*
* (c) 2020 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!
***************************************************************/
class GroupsPriceOption extends AbstractEntity implements \JsonSerializable
{
const TYPE_GLOBAL = 1;
@@ -61,33 +36,21 @@ class GroupsPriceOption extends AbstractEntity implements \JsonSerializable
*/
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;
@@ -101,49 +64,31 @@ class GroupsPriceOption extends AbstractEntity implements \JsonSerializable
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 [
@@ -2,30 +2,6 @@
namespace EP\EpProducts\Domain\Model;
/***************************************************************
*
* 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 TYPO3\CMS\Extbase\Annotation\ORM\Lazy;
use EP\EpProducts\Utility\HotelIconUtility;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
@@ -367,7 +343,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param bool $hidden
*/
public function setHidden($hidden)
public function setHidden($hidden): void
{
$this->hidden = $hidden;
}
@@ -383,7 +359,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param bool $earlybird
*/
public function setEarlybird($earlybird)
public function setEarlybird($earlybird): void
{
$this->earlybird = $earlybird;
}
@@ -399,7 +375,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param bool $new
*/
public function setNew($new)
public function setNew($new): void
{
$this->new = $new;
}
@@ -415,7 +391,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param bool $lowContingent
*/
public function setLowContingent($lowContingent)
public function setLowContingent($lowContingent): void
{
$this->lowContingent = $lowContingent;
}
@@ -431,7 +407,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param bool $showAsTeaser
*/
public function setShowAsTeaser($showAsTeaser)
public function setShowAsTeaser($showAsTeaser): void
{
$this->showAsTeaser = $showAsTeaser;
}
@@ -447,7 +423,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $detailPage
*/
public function setDetailPage($detailPage)
public function setDetailPage($detailPage): void
{
$this->detailPage = $detailPage;
}
@@ -463,7 +439,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $name
*/
public function setName($name)
public function setName($name): void
{
$this->name = $name;
}
@@ -483,7 +459,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $shortName
*/
public function setShortName($shortName)
public function setShortName($shortName): void
{
$this->shortName = $shortName;
}
@@ -499,7 +475,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $keywords
*/
public function setKeywords($keywords)
public function setKeywords($keywords): void
{
$this->keywords = $keywords;
}
@@ -514,7 +490,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $headline
*/
public function setHeadline($headline) {
public function setHeadline($headline): void {
$this->headline = $headline;
}
@@ -528,7 +504,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $teaser
*/
public function setTeaser($teaser) {
public function setTeaser($teaser): void {
$this->teaser = $teaser;
}
@@ -543,7 +519,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $headerTitle
*/
public function setHeaderTitle($headerTitle)
public function setHeaderTitle($headerTitle): void
{
$this->headerTitle = $headerTitle;
}
@@ -559,7 +535,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $headerSubtitle
*/
public function setHeaderSubtitle($headerSubtitle)
public function setHeaderSubtitle($headerSubtitle): void
{
$this->headerSubtitle = $headerSubtitle;
}
@@ -575,7 +551,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $description
*/
public function setDescription($description)
public function setDescription($description): void
{
$this->description = $description;
}
@@ -590,7 +566,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $features
*/
public function setFeatures($features) {
public function setFeatures($features): void {
$this->features = $features;
}
@@ -604,7 +580,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $roomTypes
*/
public function setRoomTypes($roomTypes) {
public function setRoomTypes($roomTypes): void {
$this->roomTypes = $roomTypes;
}
@@ -618,7 +594,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $additionalInformation
*/
public function setAdditionalInformation($additionalInformation) {
public function setAdditionalInformation($additionalInformation): void {
$this->additionalInformation = $additionalInformation;
}
@@ -637,7 +613,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param float $latitude
*/
public function setLatitude($latitude)
public function setLatitude($latitude): void
{
$this->latitude = $latitude;
}
@@ -657,7 +633,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param float $longitude
*/
public function setLongitude($longitude)
public function setLongitude($longitude): void
{
$this->longitude = $longitude;
}
@@ -673,7 +649,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $address
*/
public function setAddress($address)
public function setAddress($address): void
{
$this->address = $address;
}
@@ -686,10 +662,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->images;
}
/**
* @param ObjectStorage $images
*/
public function setImages(ObjectStorage $images)
public function setImages(ObjectStorage $images): void
{
$this->images = $images;
}
@@ -705,7 +678,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage $resellerImages
*/
public function setResellerImages($resellerImages)
public function setResellerImages($resellerImages): void
{
$this->resellerImages = $resellerImages;
}
@@ -721,7 +694,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @return null|ObjectStorage
*/
public function getTeaserImage()
public function getTeaserImage(): null|FileReference|false
{
if ($this->teaserImages->count() === 0) {
return null;
@@ -735,7 +708,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage $teaserImages
*/
public function setTeaserImages($teaserImages)
public function setTeaserImages($teaserImages): void
{
$this->teaserImages = $teaserImages;
}
@@ -748,7 +721,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
$headerImages = $this->headerImages;
if ($headerImages->count() === 0 && null !== $this->getCity()) {
$headerImages = $this->getCity()->getHeaderImages();
return $this->getCity()->getHeaderImages();
}
return $headerImages;
@@ -757,7 +730,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @return null|ObjectStorage
*/
public function getHeaderImage()
public function getHeaderImage(): null|FileReference|false
{
if ($this->headerImages->count() === 0) {
return null;
@@ -771,7 +744,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage $headerImages
*/
public function setHeaderImages($headerImages)
public function setHeaderImages($headerImages): void
{
$this->headerImages = $headerImages;
}
@@ -787,23 +760,17 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $code
*/
public function setCode($code)
public function setCode($code): void
{
$this->code = $code;
}
/**
* @param MatchCode $code
*/
public function addAdditionalCode(MatchCode $code)
public function addAdditionalCode(MatchCode $code): void
{
$this->additionalCodes->attach($code);
}
/**
* @param MatchCode $codeToRemove
*/
public function removeAdditionalCodes(MatchCode $codeToRemove)
public function removeAdditionalCodes(MatchCode $codeToRemove): void
{
$this->additionalCodes->detach($codeToRemove);
}
@@ -816,10 +783,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->additionalCodes;
}
/**
* @param ObjectStorage $codes
*/
public function setAdditionalCodes(ObjectStorage $codes)
public function setAdditionalCodes(ObjectStorage $codes): void
{
$this->additionalCodes = $codes;
}
@@ -835,7 +799,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param int $type
*/
public function setType($type)
public function setType($type): void
{
$this->type = $type;
}
@@ -863,7 +827,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param int $category
*/
public function setCategory($category)
public function setCategory($category): void
{
$this->category = $category;
}
@@ -876,10 +840,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->country;
}
/**
* @param Country $country
*/
public function setCountry(Country $country)
public function setCountry(Country $country): void
{
$this->country = $country;
}
@@ -892,10 +853,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->region;
}
/**
* @param Region $region
*/
public function setRegion(Region $region)
public function setRegion(Region $region): void
{
$this->region = $region;
}
@@ -908,18 +866,12 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->city;
}
/**
* @param City $city
*/
public function setCity(City $city)
public function setCity(City $city): void
{
$this->city = $city;
}
/**
* @param Teammember $teamer
*/
public function addTeamer(Teammember $teamer)
public function addTeamer(Teammember $teamer): void
{
$this->teamer->attach($teamer);
}
@@ -927,7 +879,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param Teammember $teamerToRemove The Teammember to be removed
*/
public function removeTeamer(Teammember $teamerToRemove)
public function removeTeamer(Teammember $teamerToRemove): void
{
$this->teamer->detach($teamerToRemove);
}
@@ -940,26 +892,17 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->teamer;
}
/**
* @param ObjectStorage $teamer
*/
public function setTeamer(ObjectStorage $teamer)
public function setTeamer(ObjectStorage $teamer): void
{
$this->teamer = $teamer;
}
/**
* @param Product $product
*/
public function addProduct(Product $product)
public function addProduct(Product $product): void
{
$this->products->attach($product);
}
/**
* @param Product $product
*/
public function removeProduct(Product $product)
public function removeProduct(Product $product): void
{
$this->products->detach($product);
}
@@ -972,33 +915,17 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->products;
}
/**
* @param ObjectStorage $products
*/
public function setProducts(ObjectStorage $products)
public function setProducts(ObjectStorage $products): void
{
$this->products = $products;
}
/**
* @param ObjectStorage $rooms
*/
public function setRooms($rooms) {
$this->rooms = $rooms;
}
/**
* @param Fact $fact
*/
public function addFact(Fact $fact)
public function addFact(Fact $fact): void
{
$this->facts->attach($fact);
}
/**
* @param Fact $factToRemove
*/
public function removeFact(Fact $factToRemove)
public function removeFact(Fact $factToRemove): void
{
$this->facts->detach($factToRemove);
}
@@ -1011,10 +938,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->facts;
}
/**
* @param ObjectStorage $facts
*/
public function setFacts(ObjectStorage $facts)
public function setFacts(ObjectStorage $facts): void
{
$this->facts = $facts;
}
@@ -1038,31 +962,22 @@ class Hotel extends AbstractEntity implements TeaserInterface
/**
* @param string $externalLink
*/
public function setExternalLink($externalLink)
public function setExternalLink($externalLink): void
{
$this->externalLink = $externalLink;
}
/**
* @return bool
*/
public function getIsApartment()
public function getIsApartment(): bool
{
return $this->getType() === self::TYPE_APPARTMENT;
}
/**
* @return bool
*/
public function getIsHolidayFlat()
public function getIsHolidayFlat(): bool
{
return $this->getType() === self::TYPE_HOLIDAY_FLAT;
}
/**
* @return bool
*/
public function getIsOther()
public function getIsOther(): bool
{
return !$this->getIsApartment() && !$this->getIsHolidayFlat();
}
@@ -1075,10 +990,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->groupsPriceConfigs;
}
/**
* @param ObjectStorage $groupPriceConfigs
*/
public function setGroupsPriceConfigs(ObjectStorage $groupPriceConfigs)
public function setGroupsPriceConfigs(ObjectStorage $groupPriceConfigs): void
{
$this->groupsPriceConfigs = $groupPriceConfigs;
}
@@ -1091,10 +1003,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->groupsPriceOptions;
}
/**
* @param ObjectStorage $groupsPriceOptions
*/
public function setGroupsPriceOptions(ObjectStorage $groupsPriceOptions)
public function setGroupsPriceOptions(ObjectStorage $groupsPriceOptions): void
{
$this->groupsPriceOptions = $groupsPriceOptions;
}
@@ -1107,10 +1016,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->groupsPriceBoards;
}
/**
* @param ObjectStorage $groupsPriceBoards
*/
public function setGroupsPriceBoards(ObjectStorage $groupsPriceBoards)
public function setGroupsPriceBoards(ObjectStorage $groupsPriceBoards): void
{
$this->groupsPriceBoards = $groupsPriceBoards;
}
@@ -1120,7 +1026,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->groupsPriceAdolescentsAge;
}
public function setGroupsPriceAdolescentsAge($groupsPriceAdolescentsAge)
public function setGroupsPriceAdolescentsAge($groupsPriceAdolescentsAge): void
{
$this->groupsPriceAdolescentsAge = $groupsPriceAdolescentsAge;
}
@@ -1130,7 +1036,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->groupsPriceTaxLabel;
}
public function setGroupsPriceTaxLabel($groupsPriceTaxLabel)
public function setGroupsPriceTaxLabel($groupsPriceTaxLabel): void
{
$this->groupsPriceTaxLabel = $groupsPriceTaxLabel;
}
@@ -1140,7 +1046,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->mainSeasonFrom;
}
public function setMainSeasonFrom(\DateTime $mainSeasonFrom)
public function setMainSeasonFrom(\DateTime $mainSeasonFrom): void
{
$this->mainSeasonFrom = $mainSeasonFrom;
}
@@ -1150,7 +1056,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->mainSeasonTo;
}
public function setMainSeasonTo(\DateTime $mainSeasonTo)
public function setMainSeasonTo(\DateTime $mainSeasonTo): void
{
$this->mainSeasonTo = $mainSeasonTo;
}
@@ -1160,7 +1066,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->bedsCount;
}
public function setBedsCount($bedsCount)
public function setBedsCount($bedsCount): void
{
$this->bedsCount = $bedsCount;
}
@@ -1170,7 +1076,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->parkingCount;
}
public function setParkingCount($parkingCount)
public function setParkingCount($parkingCount): void
{
$this->parkingCount = $parkingCount;
}
@@ -1180,7 +1086,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->altitude;
}
public function setAltitude($altitude)
public function setAltitude($altitude): void
{
$this->altitude = $altitude;
}
@@ -1190,7 +1096,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->shower;
}
public function setShower($shower)
public function setShower($shower): void
{
$this->shower = $shower;
}
@@ -1200,7 +1106,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->partyroom;
}
public function setPartyroom($partyroom)
public function setPartyroom($partyroom): void
{
$this->partyroom = $partyroom;
}
@@ -1210,7 +1116,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->gamesroom;
}
public function setGamesroom($gamesroom)
public function setGamesroom($gamesroom): void
{
$this->gamesroom = $gamesroom;
}
@@ -1220,7 +1126,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->sauna;
}
public function setSauna($sauna)
public function setSauna($sauna): void
{
$this->sauna = $sauna;
}
@@ -1230,7 +1136,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->wifi;
}
public function setWifi($wifi)
public function setWifi($wifi): void
{
$this->wifi = $wifi;
}
@@ -1240,7 +1146,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->distanceBus;
}
public function setDistanceBus($distanceBus)
public function setDistanceBus($distanceBus): void
{
$this->distanceBus = $distanceBus;
}
@@ -1250,7 +1156,7 @@ class Hotel extends AbstractEntity implements TeaserInterface
return $this->distanceLift;
}
public function setDistanceLift($distanceLift)
public function setDistanceLift($distanceLift): void
{
$this->distanceLift = $distanceLift;
}
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
@@ -33,7 +35,7 @@ class Infobox extends AbstractEntity
/**
* @param string $title
*/
public function setTitle($title)
public function setTitle($title): void
{
$this->title = $title;
}
@@ -49,7 +51,7 @@ class Infobox extends AbstractEntity
/**
* @param string $text
*/
public function setText($text)
public function setText($text): void
{
$this->text = $text;
}
@@ -65,9 +67,8 @@ class Infobox extends AbstractEntity
/**
* @param bool $defaultText
*/
public function setDefaultText($defaultText)
public function setDefaultText($defaultText): void
{
$this->defaultText = $defaultText;
}
}
@@ -1,34 +1,12 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
/***************************************************************
*
* 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!
***************************************************************/
class Journey extends AbstractEntity
{
@@ -63,7 +41,7 @@ class Journey extends AbstractEntity
/**
* @param string $name
*/
public function setName($name)
public function setName($name): void
{
$this->name = $name;
}
@@ -79,7 +57,7 @@ class Journey extends AbstractEntity
/**
* @param string $byCar
*/
public function setByCar($byCar)
public function setByCar($byCar): void
{
$this->byCar = $byCar;
}
@@ -95,7 +73,7 @@ class Journey extends AbstractEntity
/**
* @param string $byBus
*/
public function setByBus($byBus)
public function setByBus($byBus): void
{
$this->byBus = $byBus;
}
@@ -111,7 +89,7 @@ class Journey extends AbstractEntity
/**
* @param FileReference $image
*/
public function setImage($image)
public function setImage($image): void
{
$this->image = $image;
}
@@ -1,33 +1,11 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/***************************************************************
*
* 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!
***************************************************************/
class MatchCode extends AbstractEntity
{
@@ -47,9 +25,9 @@ class MatchCode extends AbstractEntity
/**
* @param string $code
*/
public function setCode($code)
public function setCode($code): void
{
$this->code = $code;
}
}
}
@@ -1,33 +1,11 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/***************************************************************
*
* 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!
***************************************************************/
class Officetip extends AbstractEntity
{
@@ -57,7 +35,7 @@ class Officetip extends AbstractEntity
/**
* @param string $label
*/
public function setLabel($label)
public function setLabel($label): void
{
$this->label = $label;
}
@@ -73,7 +51,7 @@ class Officetip extends AbstractEntity
/**
* @param string $text
*/
public function setText($text)
public function setText($text): void
{
$this->text = $text;
}
@@ -86,10 +64,7 @@ class Officetip extends AbstractEntity
return $this->author;
}
/**
* @param Teammember $author
*/
public function setAuthor(Teammember $author)
public function setAuthor(Teammember $author): void
{
$this->author = $author;
}
@@ -6,31 +6,6 @@ use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
/***************************************************************
*
* 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!
***************************************************************/
class Product extends AbstractEntity implements TeaserInterface
{
@@ -304,6 +279,11 @@ class Product extends AbstractEntity implements TeaserInterface
*/
protected $nonBookableInfo;
/**
* @var string
*/
protected $popupText = '';
public function __construct()
{
$this->initStorageObjects();
@@ -330,15 +310,12 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param bool $searchable
*/
public function setSearchable($searchable)
public function setSearchable($searchable): void
{
$this->searchable = (bool) $searchable;
}
/**
* @return boolean
*/
public function getBookable()
public function getBookable(): bool
{
return (bool) $this->bookable;
}
@@ -346,7 +323,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param boolean $bookable
*/
public function setBookable($bookable)
public function setBookable($bookable): void
{
$this->bookable = (bool) $bookable;
}
@@ -362,15 +339,12 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param bool $hideBookingButton
*/
public function setHideBookingButton($hideBookingButton)
public function setHideBookingButton($hideBookingButton): void
{
$this->hideBookingButton = (bool) $hideBookingButton;
}
/**
* @return bool
*/
public function isDaytrip()
public function isDaytrip(): bool
{
return (bool) $this->daytrip;
}
@@ -378,7 +352,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param bool $daytrip
*/
public function setDaytrip($daytrip)
public function setDaytrip($daytrip): void
{
$this->daytrip = (bool) $daytrip;
}
@@ -394,7 +368,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param bool $important
*/
public function setImportant($important)
public function setImportant($important): void
{
$this->important = $important;
}
@@ -410,7 +384,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param bool $skipassIncluded
*/
public function setSkipassIncluded($skipassIncluded)
public function setSkipassIncluded($skipassIncluded): void
{
$this->skipassIncluded = $skipassIncluded;
}
@@ -425,7 +399,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $detailPage
*/
public function setDetailPage($detailPage) {
public function setDetailPage($detailPage): void {
$this->detailPage = $detailPage;
}
@@ -440,7 +414,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $externalLink
*/
public function setExternalLink($externalLink)
public function setExternalLink($externalLink): void
{
$this->externalLink = $externalLink;
}
@@ -456,7 +430,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $name
*/
public function setName($name)
public function setName($name): void
{
$this->name = $name;
}
@@ -472,7 +446,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $nameInternal
*/
public function setNameInternal($nameInternal)
public function setNameInternal($nameInternal): void
{
$this->nameInternal = $nameInternal;
}
@@ -488,7 +462,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $keywords
*/
public function setKeywords($keywords)
public function setKeywords($keywords): void
{
$this->keywords = $keywords;
}
@@ -503,7 +477,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $headline
*/
public function setHeadline($headline) {
public function setHeadline($headline): void {
$this->headline = $headline;
}
@@ -518,7 +492,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param int $busProId
*/
public function setBusProId($busProId)
public function setBusProId($busProId): void
{
$this->busProId = $busProId;
}
@@ -534,7 +508,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $code
*/
public function setCode($code)
public function setCode($code): void
{
$this->code = $code;
}
@@ -550,7 +524,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $season
*/
public function setSeason($season)
public function setSeason($season): void
{
$this->season = $season;
}
@@ -566,7 +540,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $feature
*/
public function setFeature($feature)
public function setFeature($feature): void
{
$this->feature = $feature;
}
@@ -581,7 +555,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $teaser
*/
public function setTeaser($teaser) {
public function setTeaser($teaser): void {
$this->teaser = $teaser;
}
@@ -596,7 +570,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $teaserLong
*/
public function setTeaserLong($teaserLong)
public function setTeaserLong($teaserLong): void
{
$this->teaserLong = $teaserLong;
}
@@ -612,7 +586,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $headerTitle
*/
public function setHeaderTitle($headerTitle)
public function setHeaderTitle($headerTitle): void
{
$this->headerTitle = $headerTitle;
}
@@ -628,7 +602,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $headerSubtitle
*/
public function setHeaderSubtitle($headerSubtitle)
public function setHeaderSubtitle($headerSubtitle): void
{
$this->headerSubtitle = $headerSubtitle;
}
@@ -643,7 +617,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $conceptDescription
*/
public function setConceptDescription($conceptDescription) {
public function setConceptDescription($conceptDescription): void {
$this->conceptDescription = $conceptDescription;
}
@@ -657,7 +631,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $boardDescription
*/
public function setBoardDescription($boardDescription) {
public function setBoardDescription($boardDescription): void {
$this->boardDescription = $boardDescription;
}
@@ -671,7 +645,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $additionalServices
*/
public function setAdditionalServices($additionalServices) {
public function setAdditionalServices($additionalServices): void {
$this->additionalServices = $additionalServices;
}
@@ -686,7 +660,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $includedServices
*/
public function setIncludedServices($includedServices)
public function setIncludedServices($includedServices): void
{
$this->includedServices = $includedServices;
}
@@ -701,7 +675,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $programmeDescription
*/
public function setProgrammeDescription($programmeDescription) {
public function setProgrammeDescription($programmeDescription): void {
$this->programmeDescription = $programmeDescription;
}
@@ -716,7 +690,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $skiPassAltTitle
*/
public function setSkiPassAltTitle($skiPassAltTitle)
public function setSkiPassAltTitle($skiPassAltTitle): void
{
$this->skiPassAltTitle = $skiPassAltTitle;
}
@@ -731,7 +705,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $skiPassDescription
*/
public function setSkiPassDescription($skiPassDescription) {
public function setSkiPassDescription($skiPassDescription): void {
$this->skiPassDescription = $skiPassDescription;
}
@@ -746,7 +720,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param float $ratingAverage
*/
public function setRatingAverage($ratingAverage)
public function setRatingAverage($ratingAverage): void
{
$this->ratingAverage = $ratingAverage;
}
@@ -762,7 +736,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param int $ratingVotesCount
*/
public function setRatingVotesCount($ratingVotesCount)
public function setRatingVotesCount($ratingVotesCount): void
{
$this->ratingVotesCount = $ratingVotesCount;
}
@@ -778,7 +752,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $testimonials
*/
public function setTestimonials($testimonials)
public function setTestimonials($testimonials): void
{
$this->testimonials = $testimonials;
}
@@ -794,7 +768,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage<FileReference> $images
*/
public function setImages(ObjectStorage $images)
public function setImages(ObjectStorage $images): void
{
$this->images = $images;
}
@@ -814,7 +788,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage<FileReference> $images
*/
public function setResellerImages(ObjectStorage $images)
public function setResellerImages(ObjectStorage $images): void
{
$this->resellerImages = $images;
}
@@ -845,7 +819,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage $teaserImages
*/
public function setTeaserImages($teaserImages)
public function setTeaserImages($teaserImages): void
{
$this->teaserImages = $teaserImages;
}
@@ -861,7 +835,7 @@ class Product extends AbstractEntity implements TeaserInterface
$headerImages = $this->headerImages;
if ($headerImages->count() === 0 && null !== $this->getRegion()) {
$headerImages = $this->getRegion()->getHeaderImages();
return $this->getRegion()->getHeaderImages();
}
return $headerImages;
@@ -888,7 +862,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage $headerImages
*/
public function setHeaderImages($headerImages)
public function setHeaderImages($headerImages): void
{
$this->headerImages = $headerImages;
}
@@ -901,10 +875,7 @@ class Product extends AbstractEntity implements TeaserInterface
return $this->concept;
}
/**
* @param Concept $concept
*/
public function setConcepts(Concept $concept)
public function setConcepts(Concept $concept): void
{
$this->concept = $concept;
}
@@ -917,10 +888,7 @@ class Product extends AbstractEntity implements TeaserInterface
return $this->country;
}
/**
* @param Country $country
*/
public function setCountry(Country $country)
public function setCountry(Country $country): void
{
$this->country = $country;
}
@@ -933,10 +901,7 @@ class Product extends AbstractEntity implements TeaserInterface
return $this->region;
}
/**
* @param Region $region
*/
public function setRegion(Region $region)
public function setRegion(Region $region): void
{
$this->region = $region;
}
@@ -949,10 +914,7 @@ class Product extends AbstractEntity implements TeaserInterface
return $this->city;
}
/**
* @param City $city
*/
public function setCity(City $city)
public function setCity(City $city): void
{
$this->city = $city;
}
@@ -968,7 +930,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param Journey $journey
*/
public function setJourney($journey)
public function setJourney($journey): void
{
$this->journey = $journey;
}
@@ -984,23 +946,17 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage<Fact> $facts
*/
public function setFacts(ObjectStorage $facts)
public function setFacts(ObjectStorage $facts): void
{
$this->facts = $facts;
}
/**
* @param Hotel $hotel
*/
public function addHotel(Hotel $hotel)
public function addHotel(Hotel $hotel): void
{
$this->hotels->attach($hotel);
}
/**
* @param Hotel $hotel
*/
public function removeHotel(Hotel $hotel)
public function removeHotel(Hotel $hotel): void
{
$this->hotels->detach($hotel);
}
@@ -1016,7 +972,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage<Hotel> $hotels
*/
public function setHotels(ObjectStorage $hotels)
public function setHotels(ObjectStorage $hotels): void
{
$this->hotels = $hotels;
}
@@ -1031,7 +987,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param ObjectStorage<Faq> $faq
*/
public function setFaqs(ObjectStorage $faqs)
public function setFaqs(ObjectStorage $faqs): void
{
$this->faqs = $faqs;
}
@@ -1047,7 +1003,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param Officetip $officetip
*/
public function setOfficetip($officetip)
public function setOfficetip($officetip): void
{
$this->officetip = $officetip;
}
@@ -1063,7 +1019,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param Hotel $calendarHotel
*/
public function setCalendarHotel($calendarHotel)
public function setCalendarHotel($calendarHotel): void
{
$this->calendarHotel = $calendarHotel;
}
@@ -1079,7 +1035,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param Product $altProduct
*/
public function setAltProduct($altProduct)
public function setAltProduct($altProduct): void
{
$this->altProduct = $altProduct;
}
@@ -1095,7 +1051,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $altLabel
*/
public function setAltLabel($altLabel)
public function setAltLabel($altLabel): void
{
$this->altLabel = $altLabel;
}
@@ -1111,7 +1067,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param string $video
*/
public function setVideo($video)
public function setVideo($video): void
{
$this->video = $video;
}
@@ -1127,7 +1083,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param FileReference $banner
*/
public function setBanner($banner)
public function setBanner($banner): void
{
$this->banner = $banner;
}
@@ -1143,7 +1099,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param Badge $badge
*/
public function setBadge($badge)
public function setBadge($badge): void
{
$this->badge = $badge;
}
@@ -1156,9 +1112,6 @@ class Product extends AbstractEntity implements TeaserInterface
return $this->nonBookableDates;
}
/**
* @return int
*/
public function getMinPrice(): int
{
return $this->minPrice;
@@ -1167,7 +1120,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param int $minPrice
*/
public function setMinPrice($minPrice)
public function setMinPrice($minPrice): void
{
$this->minPrice = $minPrice;
}
@@ -1183,7 +1136,7 @@ class Product extends AbstractEntity implements TeaserInterface
/**
* @param Infobox $nonBookableInfo
*/
public function setNonBookableInfo($nonBookableInfo)
public function setNonBookableInfo($nonBookableInfo): void
{
$this->nonBookableInfo = $nonBookableInfo;
}
@@ -1193,17 +1146,29 @@ class Product extends AbstractEntity implements TeaserInterface
return $this->tourThumbnail;
}
public function setTourThumbnail(FileReference $tourThumbnail)
public function setTourThumbnail(FileReference $tourThumbnail): void
{
$this->tourThumbnail = $tourThumbnail;
}
public function getPopupText()
{
return $this->popupText;
}
public function setPopupText($popupText): static
{
$this->popupText = $popupText;
return $this;
}
public function getTourLink()
{
return $this->tourLink;
}
public function setTourLink(string $tourLink)
public function setTourLink(string $tourLink): void
{
$this->tourLink = $tourLink;
}
@@ -6,30 +6,6 @@ use TYPO3\CMS\Extbase\Annotation\ORM\Cascade;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
/***************************************************************
*
* 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!
***************************************************************/
class Region extends AbstractDestination implements TeaserInterface
{
@@ -157,7 +133,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param bool $excludeFromTeasergroups
*/
public function setExcludeFromTeasergroups($excludeFromTeasergroups)
public function setExcludeFromTeasergroups($excludeFromTeasergroups): void
{
$this->excludeFromTeasergroups = (bool) $excludeFromTeasergroups;
}
@@ -172,7 +148,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param string $skiArea
*/
public function setSkiArea($skiArea) {
public function setSkiArea($skiArea): void {
$this->skiArea = $skiArea;
}
@@ -187,7 +163,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param string $skiAreaExtended
*/
public function setSkiAreaExtended($skiAreaExtended)
public function setSkiAreaExtended($skiAreaExtended): void
{
$this->skiAreaExtended = $skiAreaExtended;
}
@@ -202,7 +178,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param string $skiPass
*/
public function setSkiPass($skiPass) {
public function setSkiPass($skiPass): void {
$this->skiPass = $skiPass;
}
@@ -216,7 +192,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param string $news
*/
public function setNews($news) {
public function setNews($news): void {
$this->news = $news;
}
@@ -230,7 +206,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param string $length
*/
public function setLength($length) {
public function setLength($length): void {
$this->length = $length;
}
@@ -244,7 +220,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param string $height
*/
public function setHeight($height) {
public function setHeight($height): void {
$this->height = $height;
}
@@ -258,7 +234,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param string $lifts
*/
public function setLifts($lifts) {
public function setLifts($lifts): void {
$this->lifts = $lifts;
}
@@ -273,7 +249,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param string $feature
*/
public function setFeature($feature)
public function setFeature($feature): void
{
$this->feature = $feature;
}
@@ -289,7 +265,7 @@ class Region extends AbstractDestination implements TeaserInterface
$headerImages = $this->headerImages;
if ($headerImages->count() === 0 && null !== $this->getCountry()) {
$headerImages = $this->getCountry()->getHeaderImages();
return $this->getCountry()->getHeaderImages();
}
return $headerImages;
@@ -303,10 +279,7 @@ class Region extends AbstractDestination implements TeaserInterface
return $this->country;
}
/**
* @param Country $country
*/
public function setCountry(Country $country)
public function setCountry(Country $country): void
{
$this->country = $country;
}
@@ -321,7 +294,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param ObjectStorage $cities
*/
public function setCities($cities) {
public function setCities($cities): void {
$this->cities = $cities;
}
@@ -335,7 +308,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param ObjectStorage $products
*/
public function setProducts($products) {
public function setProducts($products): void {
$this->products = $products;
}
@@ -352,7 +325,7 @@ class Region extends AbstractDestination implements TeaserInterface
*
* @param ObjectStorage<Fact> $facts
*/
public function setFacts(ObjectStorage $facts)
public function setFacts(ObjectStorage $facts): void
{
$this->facts = $facts;
}
@@ -367,7 +340,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param ObjectStorage $hotels
*/
public function setHotels($hotels) {
public function setHotels($hotels): void {
$this->hotels = $hotels;
}
@@ -382,7 +355,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param Snowreport $snowreport
*/
public function setSnowreport($snowreport)
public function setSnowreport($snowreport): void
{
$this->snowreport = $snowreport;
}
@@ -395,10 +368,7 @@ class Region extends AbstractDestination implements TeaserInterface
return $this->webcam;
}
/**
* @return bool
*/
public function getWebcamAvailable()
public function getWebcamAvailable(): bool
{
$webcam = $this->getWebcam();
return $webcam !== null && $webcam->getAvailable();
@@ -407,7 +377,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param Webcam $webcam
*/
public function setWebcam($webcam)
public function setWebcam($webcam): void
{
$this->webcam = $webcam;
}
@@ -423,7 +393,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference>
*/
public function setRegionMaps($regionMaps)
public function setRegionMaps($regionMaps): void
{
$this->regionMaps = $regionMaps;
}
@@ -439,7 +409,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param FileReference $awardImage
*/
public function setAwardImage($awardImage)
public function setAwardImage($awardImage): void
{
$this->awardImage = $awardImage;
}
@@ -455,7 +425,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param string $awardLink
*/
public function setAwardLink($awardLink)
public function setAwardLink($awardLink): void
{
$this->awardLink = $awardLink;
}
@@ -471,7 +441,7 @@ class Region extends AbstractDestination implements TeaserInterface
/**
* @param string $awardLabel
*/
public function setAwardLabel($awardLabel)
public function setAwardLabel($awardLabel): void
{
$this->awardLabel = $awardLabel;
}
@@ -6,31 +6,6 @@ use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Annotation\ORM\Lazy;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/***************************************************************
*
* Copyright notice
*
* (c) 2018 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!
***************************************************************/
class Reseller extends AbstractEntity
{
@@ -71,7 +46,7 @@ class Reseller extends AbstractEntity
/**
* @param string $name
*/
public function setName($name)
public function setName($name): void
{
$this->name = $name;
}
@@ -87,7 +62,7 @@ class Reseller extends AbstractEntity
/**
* @param string $code
*/
public function setCode($code)
public function setCode($code): void
{
$this->code = $code;
}
@@ -103,7 +78,7 @@ class Reseller extends AbstractEntity
/**
* @param string $paCode
*/
public function setPaCode($paCode)
public function setPaCode($paCode): void
{
$this->paCode = $paCode;
}
@@ -119,7 +94,7 @@ class Reseller extends AbstractEntity
/**
* @param ObjectStorage $products
*/
public function setProducts($products)
public function setProducts($products): void
{
$this->products = $products;
}
@@ -1,31 +1,8 @@
<?php
namespace EP\EpProducts\Domain\Model;
declare(strict_types=1);
/***************************************************************
*
* 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!
***************************************************************/
namespace EP\EpProducts\Domain\Model;
class Room
{
@@ -1,53 +1,17 @@
<?php
namespace EP\EpProducts\Domain\Model;
declare(strict_types=1);
/***************************************************************
*
* 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!
***************************************************************/
namespace EP\EpProducts\Domain\Model;
class SearchResult
{
/**
* @var int
*/
protected $total = 0;
/**
* @var array
*/
protected $data = [];
/**
* @param array $data
* @param int $total
*/
public function __construct(array $data = [], $total = 0)
public function __construct(private array $data = [], private $total = 0)
{
$this->data = $data;
$this->total = $total;
}
/**
@@ -61,23 +25,17 @@ class SearchResult
/**
* @param int $total
*/
public function setTotal($total)
public function setTotal($total): void
{
$this->total = $total;
}
/**
* @return array
*/
public function getData()
public function getData(): array
{
return $this->data;
}
/**
* @param array $data
*/
public function setData($data)
public function setData(array $data): void
{
$this->data = $data;
}
@@ -5,31 +5,6 @@ namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/***************************************************************
*
* 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!
***************************************************************/
class Snowreport extends AbstractEntity
{
@@ -129,7 +104,7 @@ class Snowreport extends AbstractEntity
/**
* @param int $vendorUid
*/
public function setVendorUid($vendorUid)
public function setVendorUid($vendorUid): void
{
$this->vendorUid = $vendorUid;
}
@@ -145,7 +120,7 @@ class Snowreport extends AbstractEntity
/**
* @param string $regionName
*/
public function setRegionName($regionName)
public function setRegionName($regionName): void
{
$this->regionName = $regionName;
}
@@ -161,7 +136,7 @@ class Snowreport extends AbstractEntity
/**
* @param bool $glacier
*/
public function setGlacier($glacier)
public function setGlacier($glacier): void
{
$this->glacier = $glacier;
}
@@ -177,7 +152,7 @@ class Snowreport extends AbstractEntity
/**
* @param int $heightMax
*/
public function setHeightMax($heightMax)
public function setHeightMax($heightMax): void
{
$this->heightMax = $heightMax;
}
@@ -193,7 +168,7 @@ class Snowreport extends AbstractEntity
/**
* @param int $heightMin
*/
public function setHeightMin($heightMin)
public function setHeightMin($heightMin): void
{
$this->heightMin = $heightMin;
}
@@ -209,7 +184,7 @@ class Snowreport extends AbstractEntity
/**
* @param bool $opened
*/
public function setOpened($opened)
public function setOpened($opened): void
{
$this->opened = $opened;
}
@@ -225,7 +200,7 @@ class Snowreport extends AbstractEntity
/**
* @param int $snowLevelMountain
*/
public function setSnowLevelMountain($snowLevelMountain)
public function setSnowLevelMountain($snowLevelMountain): void
{
$this->snowLevelMountain = $snowLevelMountain;
}
@@ -241,7 +216,7 @@ class Snowreport extends AbstractEntity
/**
* @param int $snowLevelValley
*/
public function setSnowLevelValley($snowLevelValley)
public function setSnowLevelValley($snowLevelValley): void
{
$this->snowLevelValley = $snowLevelValley;
}
@@ -257,7 +232,7 @@ class Snowreport extends AbstractEntity
/**
* @param string $snowQuality
*/
public function setSnowQuality($snowQuality)
public function setSnowQuality($snowQuality): void
{
$this->snowQuality = $snowQuality;
}
@@ -273,15 +248,12 @@ class Snowreport extends AbstractEntity
/**
* @param \DateTime $lastSnowfall
*/
public function setLastSnowfall($lastSnowfall)
public function setLastSnowfall($lastSnowfall): void
{
$this->lastSnowfall = $lastSnowfall;
}
/**
* @return int
*/
public function getLiftsTotal()
public function getLiftsTotal(): int
{
return (int) $this->liftsTotal;
}
@@ -289,15 +261,12 @@ class Snowreport extends AbstractEntity
/**
* @param int $liftsTotal
*/
public function setLiftsTotal($liftsTotal)
public function setLiftsTotal($liftsTotal): void
{
$this->liftsTotal = $liftsTotal;
}
/**
* @return int
*/
public function getLiftsOpened()
public function getLiftsOpened(): int
{
return (int) $this->liftsOpened;
}
@@ -305,7 +274,7 @@ class Snowreport extends AbstractEntity
/**
* @param int $liftsOpened
*/
public function setLiftsOpened($liftsOpened)
public function setLiftsOpened($liftsOpened): void
{
$this->liftsOpened = $liftsOpened;
}
@@ -321,23 +290,17 @@ class Snowreport extends AbstractEntity
/**
* @param \DateTime $lastUpdate
*/
public function setLastUpdate($lastUpdate)
public function setLastUpdate($lastUpdate): void
{
$this->lastUpdate = $lastUpdate;
}
/**
* @param Webcam $webcam
*/
public function addWebcam(Webcam $webcam)
public function addWebcam(Webcam $webcam): void
{
$this->webcams->attach($webcam);
}
/**
* @param Webcam $webcam
*/
public function removeWebcam(Webcam $webcam)
public function removeWebcam(Webcam $webcam): void
{
$this->webcams->detach($webcam);
}
@@ -353,7 +316,7 @@ class Snowreport extends AbstractEntity
/**
* @param ObjectStorage<Webcam> $webcams
*/
public function setWebcams(ObjectStorage $webcams)
public function setWebcams(ObjectStorage $webcams): void
{
$this->webcams = $webcams;
}
@@ -369,7 +332,7 @@ class Snowreport extends AbstractEntity
/**
* @param \DateTime $seasonStart
*/
public function setSeasonStart($seasonStart)
public function setSeasonStart($seasonStart): void
{
$this->seasonStart = $seasonStart;
}
@@ -385,7 +348,7 @@ class Snowreport extends AbstractEntity
/**
* @param \DateTime $seasonEnd
*/
public function setSeasonEnd($seasonEnd)
public function setSeasonEnd($seasonEnd): void
{
$this->seasonEnd = $seasonEnd;
}
@@ -7,31 +7,6 @@ use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\Domain\Model\Category;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/***************************************************************
*
* 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!
***************************************************************/
class Teammember extends AbstractEntity
{
@@ -86,7 +61,7 @@ class Teammember extends AbstractEntity
/**
* @param string $name
*/
public function setName($name)
public function setName($name): void
{
$this->name = $name;
}
@@ -102,7 +77,7 @@ class Teammember extends AbstractEntity
/**
* @param string $email
*/
public function setEmail($email)
public function setEmail($email): void
{
$this->email = $email;
}
@@ -118,7 +93,7 @@ class Teammember extends AbstractEntity
/**
* @param string $phone
*/
public function setPhone($phone)
public function setPhone($phone): void
{
$this->phone = $phone;
}
@@ -134,7 +109,7 @@ class Teammember extends AbstractEntity
/**
* @param string $tasks
*/
public function setTasks($tasks)
public function setTasks($tasks): void
{
$this->tasks = $tasks;
}
@@ -150,7 +125,7 @@ class Teammember extends AbstractEntity
/**
* @param FileReference $image
*/
public function setImage($image)
public function setImage($image): void
{
$this->image = $image;
}
@@ -166,7 +141,7 @@ class Teammember extends AbstractEntity
/**
* @param string $description
*/
public function setDescription($description)
public function setDescription($description): void
{
$this->description = $description;
}
@@ -182,7 +157,7 @@ class Teammember extends AbstractEntity
/**
* @param ObjectStorage $categories
*/
public function setCategories($categories)
public function setCategories($categories): void
{
$this->categories = $categories;
}
@@ -1,34 +1,11 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/***************************************************************
*
* 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!
***************************************************************/
interface TeaserInterface
{
@@ -121,7 +121,7 @@ class Travelinfo extends AbstractEntity
/**
* @param string $title
*/
public function setTitle($title)
public function setTitle($title): void
{
$this->title = $title;
}
@@ -131,7 +131,7 @@ class Travelinfo extends AbstractEntity
return $this->headerImage;
}
public function setHeaderImage($headerImage)
public function setHeaderImage($headerImage): static
{
$this->headerImage = $headerImage;
@@ -143,7 +143,7 @@ class Travelinfo extends AbstractEntity
return $this->footerImage;
}
public function setFooterImage($footerImage)
public function setFooterImage($footerImage): static
{
$this->footerImage = $footerImage;
@@ -155,7 +155,7 @@ class Travelinfo extends AbstractEntity
return $this->subline;
}
public function setSubline($subline)
public function setSubline($subline): static
{
$this->subline = $subline;
@@ -167,7 +167,7 @@ class Travelinfo extends AbstractEntity
return $this->headline;
}
public function setHeadline($headline)
public function setHeadline($headline): static
{
$this->headline = $headline;
@@ -179,7 +179,7 @@ class Travelinfo extends AbstractEntity
return $this->introText;
}
public function setIntroText($introText)
public function setIntroText($introText): static
{
$this->introText = $introText;
@@ -191,7 +191,7 @@ class Travelinfo extends AbstractEntity
return $this->busInfoText;
}
public function setBusInfoText($busInfoText)
public function setBusInfoText($busInfoText): static
{
$this->busInfoText = $busInfoText;
@@ -203,7 +203,7 @@ class Travelinfo extends AbstractEntity
return $this->departureText;
}
public function setDepartureText($departureText)
public function setDepartureText($departureText): static
{
$this->departureText = $departureText;
@@ -215,7 +215,7 @@ class Travelinfo extends AbstractEntity
return $this->footerText;
}
public function setFooterText($footerText)
public function setFooterText($footerText): static
{
$this->footerText = $footerText;
@@ -227,7 +227,7 @@ class Travelinfo extends AbstractEntity
return $this->additionalInfo;
}
public function setAdditionalInfo($additionalInfo)
public function setAdditionalInfo($additionalInfo): static
{
$this->additionalInfo = $additionalInfo;
@@ -239,7 +239,7 @@ class Travelinfo extends AbstractEntity
return $this->importantPhoneNumbers;
}
public function setImportantPhoneNumbers($importantPhoneNumbers)
public function setImportantPhoneNumbers($importantPhoneNumbers): static
{
$this->importantPhoneNumbers = $importantPhoneNumbers;
@@ -251,7 +251,7 @@ class Travelinfo extends AbstractEntity
return $this->importantLinks;
}
public function setImportantLinks($importantLinks)
public function setImportantLinks($importantLinks): static
{
$this->importantLinks = $importantLinks;
@@ -263,7 +263,7 @@ class Travelinfo extends AbstractEntity
return $this->selfArrangedText;
}
public function setSelfArrangedText($selfArrangedText)
public function setSelfArrangedText($selfArrangedText): static
{
$this->selfArrangedText = $selfArrangedText;
@@ -275,7 +275,7 @@ class Travelinfo extends AbstractEntity
return $this->teasers;
}
public function setTeasers($teasers)
public function setTeasers($teasers): void
{
$this->teasers = $teasers;
}
@@ -285,7 +285,7 @@ class Travelinfo extends AbstractEntity
return $this->links;
}
public function setLinks($links)
public function setLinks($links): static
{
$this->links = $links;
@@ -297,7 +297,7 @@ class Travelinfo extends AbstractEntity
return $this->travelCode;
}
public function setTravelCode($travelCode)
public function setTravelCode($travelCode): static
{
$this->travelCode = $travelCode;
@@ -309,7 +309,7 @@ class Travelinfo extends AbstractEntity
return $this->product;
}
public function setProduct($product)
public function setProduct($product): static
{
$this->product = $product;
return $this;
@@ -320,7 +320,7 @@ class Travelinfo extends AbstractEntity
return $this->hideAddress;
}
public function setHideAddress($hideAddress)
public function setHideAddress($hideAddress): static
{
$this->hideAddress = $hideAddress;
return $this;
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
@@ -32,7 +34,7 @@ class TravelinfoLink extends AbstractEntity
/**
* @param string $title
*/
public function setTitle($title)
public function setTitle($title): void
{
$this->title = $title;
}
@@ -42,7 +44,7 @@ class TravelinfoLink extends AbstractEntity
return $this->link;
}
public function setLink($link)
public function setLink($link): void
{
$this->link = $link;
}
@@ -52,9 +54,8 @@ class TravelinfoLink extends AbstractEntity
return $this->icon;
}
public function setIcon($icon)
public function setIcon($icon): void
{
$this->icon = $icon;
}
}
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
@@ -38,7 +40,7 @@ class TravelinfoTeaser extends AbstractEntity
/**
* @param string $title
*/
public function setTitle($title)
public function setTitle($title): void
{
$this->title = $title;
}
@@ -48,7 +50,7 @@ class TravelinfoTeaser extends AbstractEntity
return $this->bodytext;
}
public function setBodytext($bodytext)
public function setBodytext($bodytext): void
{
$this->bodytext = $bodytext;
}
@@ -58,7 +60,7 @@ class TravelinfoTeaser extends AbstractEntity
return $this->category;
}
public function setCategory($category)
public function setCategory($category): void
{
$this->category = $category;
}
@@ -68,9 +70,8 @@ class TravelinfoTeaser extends AbstractEntity
return $this->image;
}
public function setImage($image)
public function setImage($image): void
{
$this->image = $image;
}
}
@@ -1,33 +1,11 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
/***************************************************************
*
* 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!
***************************************************************/
class Webcam extends AbstractEntity
{
@@ -67,7 +45,7 @@ class Webcam extends AbstractEntity
/**
* @param Snowreport $snowreport
*/
public function setSnowreport($snowreport)
public function setSnowreport($snowreport): void
{
$this->snowreport = $snowreport;
}
@@ -83,7 +61,7 @@ class Webcam extends AbstractEntity
/**
* @param int $vendorUid
*/
public function setVendorUid($vendorUid)
public function setVendorUid($vendorUid): void
{
$this->vendorUid = $vendorUid;
}
@@ -99,7 +77,7 @@ class Webcam extends AbstractEntity
/**
* @param string $name
*/
public function setName($name)
public function setName($name): void
{
$this->name = $name;
}
@@ -115,7 +93,7 @@ class Webcam extends AbstractEntity
/**
* @param string $url
*/
public function setUrl($url)
public function setUrl($url): void
{
$this->url = $url;
}
@@ -131,9 +109,8 @@ class Webcam extends AbstractEntity
/**
* @param boolean $available
*/
public function setAvailable($available)
public function setAvailable($available): void
{
$this->available = $available;
}
}
@@ -25,7 +25,6 @@ abstract class AbstractRepository extends Repository
}
/**
* @param array $uids
* @return QueryResultInterface
* @throws InvalidQueryException
*/
@@ -96,7 +95,7 @@ abstract class AbstractRepository extends Repository
protected function addHotelFilterConditions(QueryBuilder $qb, array $filterSettings): void
{
// Hotel
if ($filterSettings['hotelUid'] > 0) {
if (($filterSettings['hotelUid'] ?? 0) > 0) {
$qb->andWhere($qb->expr()->eq(
'date.hotel',
$qb->createNamedParameter((int)$filterSettings['hotelUid'], Connection::PARAM_INT)
@@ -4,38 +4,15 @@ namespace EP\EpProducts\Domain\Repository;
use EP\EpProducts\Domain\Model\AbstractDestination;
/***************************************************************
*
* 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!
***************************************************************/
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;
}
public function getTeasersByCountryOrRegionAndSeason(
int $regionUid = 0,
int $countryUid = 0,
string $season = 'w',
int $storagePid = 0,
) {
$season ??= AbstractDestination::SEASON_WINTER;
$query = $this->createQuery();
$constraints = [
$query->equals('season', $season),
@@ -1,31 +1,8 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Repository;
/***************************************************************
*
* 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!
***************************************************************/
class ConceptRepository extends AbstractRepository
{}
@@ -1,31 +1,8 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Domain\Repository;
/***************************************************************
*
* 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!
***************************************************************/
class CountryRepository extends AbstractRepository
{}
@@ -0,0 +1,38 @@
<?php
namespace EP\EpProducts\Domain\Repository;
use EP\EpProducts\Domain\Model\Faq;
use Doctrine\DBAL\ArrayParameterType;
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 = GeneralUtility::makeInstance(DataMapper::class);
$uids = GeneralUtility::intExplode(',', (string)$uids, true);
if ($uids === []) {
return [];
}
$qb = $this->getDbConnection()->createQueryBuilder();
$qb
->select('*')
->from('tx_epproducts_domain_model_faq')
->where('uid IN (:uids)')
->setParameter('uids', $uids, ArrayParameterType::INTEGER);
$qb->getConcreteQueryBuilder()->orderBy('FIELD(uid, ' . implode(',', $uids) . ')');
$rows = $qb->executeQuery()->fetchAllAssociative();
return $dataMapper->map(Faq::class, $rows);
}
}
@@ -2,30 +2,6 @@
namespace EP\EpProducts\Domain\Repository;
/***************************************************************
*
* 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 TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException;
@@ -41,7 +17,7 @@ class HotelRepository extends AbstractRepository
* @return array
* @throws \Doctrine\DBAL\DBALException
*/
public function getSingleByUid($hotelUid)
public function getSingleByUid($hotelUid): array|false
{
$qb = $this->getQueryBuilder();
$qb
@@ -59,7 +35,7 @@ class HotelRepository extends AbstractRepository
* @return array
* @throws \Doctrine\DBAL\DBALException
*/
public function getDateIndependentSingleByUid($hotelUid)
public function getDateIndependentSingleByUid($hotelUid): array|false
{
$qb = $this->getDbConnection()->createQueryBuilder();
@@ -83,10 +59,9 @@ class HotelRepository extends AbstractRepository
/**
* @param int $productUid
* @return array
* @throws \Doctrine\DBAL\DBALException
*/
public function getListByProduct($productUid)
public function getListByProduct($productUid): array
{
$qb = $this->getQueryBuilder(false);
$qb
@@ -98,11 +73,9 @@ class HotelRepository extends AbstractRepository
}
/**
* @param array $criteria
* @return array
* @throws \Doctrine\DBAL\DBALException
*/
public function getListByCriteria(array $criteria)
public function getListByCriteria(array $criteria): array
{
$optionsResolver = new OptionsResolver();
$optionsResolver->setDefined(['regionUid', 'countryUid', 'conceptUid', 'productUid', 'category', 'type']);
@@ -150,12 +123,9 @@ class HotelRepository extends AbstractRepository
}
/**
* @param array $criteria
* @param array $excludedHotelUids
* @return array
* @throws \Doctrine\DBAL\DBALException
*/
public function getDateIndependentListByCriteria(array $criteria, array $excludedHotelUids = [])
public function getDateIndependentListByCriteria(array $criteria, array $excludedHotelUids = []): array
{
$optionsResolver = new OptionsResolver();
$optionsResolver->setDefined(['regionUid', 'countryUid', 'conceptUid', 'productUid', 'category', 'type']);
@@ -179,7 +149,7 @@ class HotelRepository extends AbstractRepository
$qb->getConcreteQueryBuilder()->orderBy('FIELD(hotel.type, ' . Hotel::TYPE_SPORTS_CLUB . ') DESC');
$qb->addOrderBy('hotel.name', 'ASC');
if ($excludedHotelUids) {
if ($excludedHotelUids !== []) {
$qb
->andWhere('hotel.uid NOT IN (:excludedHotelUids)')
->setParameter('excludedHotelUids', $excludedHotelUids, Connection::PARAM_INT_ARRAY)
@@ -225,7 +195,7 @@ class HotelRepository extends AbstractRepository
* @return QueryBuilder
* @throws \Doctrine\DBAL\DBALException
*/
protected function getQueryBuilder($excludeNonSearchable = true)
private function getQueryBuilder($excludeNonSearchable = true)
{
$qb = $this->getDbConnection()->createQueryBuilder();
@@ -244,7 +244,7 @@ class ProductRepository extends AbstractRepository
);
}
if ($date !== null) {
if ($date instanceof Date) {
$qb->andWhere($qb->expr()->eq(
'date.uid',
$qb->createNamedParameter($date->getUid(), Connection::PARAM_INT))
@@ -4,38 +4,11 @@ namespace EP\EpProducts\Domain\Repository;
use EP\EpProducts\Domain\Model\AbstractDestination;
/***************************************************************
*
* 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!
***************************************************************/
class RegionRepository extends AbstractRepository
{
public function getTeasersByCountryAndSeason(int $countryUid, string $season = 'w', int $storagePid = 0)
{
if ($season === null) {
$season = AbstractDestination::SEASON_WINTER;
}
$season ??= AbstractDestination::SEASON_WINTER;
$query = $this->createQuery();
$constraints = [
$query->equals('country', $countryUid),
@@ -31,7 +31,7 @@ class SnowreportRepository extends AbstractRepository
->orderBy('region.name')
;
if ($regionUids) {
if ($regionUids !== []) {
$qb->andWhere($qb->expr()->in(
'region.uid',
$qb->createNamedParameter($regionUids, Connection::PARAM_INT_ARRAY)
@@ -0,0 +1,30 @@
<?php
namespace EP\EpProducts\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException;
class TeammemberRepository extends AbstractRepository
{
/**
* @var array
*/
#[\Override]
protected $defaultOrderings = [
'sorting' => 'ASC',
];
/**
* @return array|QueryResultInterface
* @throws InvalidQueryException
*/
public function findByCategories(array $categories = [])
{
$qb = $this->createQuery();
$qb->matching($qb->in('categories.uid', $categories));
return $qb->execute();
}
}
Loaded 100 of 422 files, more files were not shown because too many files have changed in this diff. Show more