5.0.0
Breaking
- Support for TYPO3 v12 removed.
-
Category and keyword anchors moved from the import configuration to the target site, and are now scoped by import target. The flexform fields
categoryStoragePid,categoryParent,keywordStoragePidandkeywordParentare removed without replacement; values left in stored flexform XML are ignored. Configure the anchors in the site settings instead — eight settings, one set per target, because a site may hold an import configuration of each and their category trees have to stay apart:import.thuecat.category.storagePid,import.thuecat.category.parent,import.thuecat.keywords.storagePid,import.thuecat.keywords.parentimport.events.category.storagePid,import.events.category.parent,import.events.keywords.storagePid,import.events.keywords.parent
A site opts into the settings editor for them by listing the
werkraummedia/thuecat-importset among its owndependencies. The anchors are no longer declared bythuecat_cesorevents_ces. On installations without a site set they remain configurable instance-wide in the Extension Configuration, where the fourimportCategory*/importKeywords*keys are likewise replaced by eightimportThuecat*/importEvents*ones.There is no upgrade wizard: note the existing values before updating, because an import whose anchors are not configured runs with category and keyword mapping switched off rather than failing.
Check the
importTargetof every import configuration while you are there. It decides which anchors a run resolves, and a configuration that writes event records without it set counts as thethuecattarget — putting its categories in the ThueCat tree. A value matching no known target now aborts the run before it fetches anything, instead of importing with no categories. See Category based anchors. - An imported record is identified within the site that imports it. Previously a
remote_idmatched any record carrying it anywhere in the installation, so two sites importing the same upstream object shared one record: the second site's import updated the first site's record in place — the record kept its page, so one site silently edited another's content — and the second site never got a record of its own. Each site now holds its own record, and relation targets resolve within the importing site too. On an installation where two sites import overlapping data, the first run after the update creates the records the second site was missing, which looks like duplication but is each site gaining what it should always have had. There is no migration: records a foreign site's import wrote into keep whatever it last wrote, and editors reconcile them. - API responses are cached persistently:
thuecat_fetchdatanow defaults toTypo3Databasewith a 900 second lifetime instead of dying with the process. A run is no longer guaranteed to hit the API — see Fetch cache for the bypass options.Backend - Imported images are now the original asset (
schema:contentUrloverschema:url), stored under a filename derived from the download URL. Re-imported records point at a different file under a new name; the previously imported files stay on disk unreferenced. No migration pass. See Import. - The list item moved from
Partials/toTourist Attraction/ List Item. html Templates/, and the list templates now receive rendered items inTourist Attraction/ List Item. html {items}rather than iterating attractions. An override left inPartials/is silently ignored. See Frontend output. - The category and keyword fields on tourist attractions and tourist information are bounded by
the site's anchors instead of offering every
sys_in the installation. A site that configures no anchor now offers nothing in these fields, where it previously offered everything; configurecategory import.thuecat.category.parentandimport.thuecat.keywords.parentto restore a selection. The same bound applies to the keyword field of the attraction list plugin. See Category based anchors. -
Filter options are built by
Search. Removed without replacement:Filter Options Service Tourist,Attraction Repository:: find Category Tree For Search Form () find,Keywords Tree For Search Form () find,Towns In Storage Sorted By Title () Town, and the frontendRepository:: find All For Search Form Sorted By Title () Category.Repository \Dto\is replaced byCategory Node \Dto\, which keeps its accessor names — template overrides need no change.Filter Option The partial
Partials/requires aTourist Attraction/ Category Tree. html propertyargument naming the demand property its checkboxes bind to; an override without it renders unbound checkboxes. Its element ids carry that property too, so a category and a keyword sharing a uid no longer collide. - Filter options are scoped by site, not by the list's storage pages. A value outside those pages but inside the site is now offered; a value from another site no longer is.
- A record can be contained in more than one place.
townis a multi-value relation instead of a single select, soTouristis replaced byAttraction:: get Town () get, returning anTowns () Object. Templates readingStorage {attraction.must iteratetown. title} {attraction.instead. Existing rows keep the town they held; a row that held none reads as an empty relation. Applies to tourist attractions, tourist information and parking facilities alike. See One property, several target tables.towns} - Every transient bucket reports a reference it cannot relate. Previously only
containedInPlaceloggedreferenceUnrelatable;managedByandparkingFacilityNearBydropped a reference whose record imported into another table without saying so. Expect newinfoentries in the import log from those buckets on the first run — they name relations that were already being lost, not new failures.
Features
- Support for TYPO3 v14 added.
- Images and other files are imported via FAL; the target file directory is part of the import configuration. See Import.
- One asset is downloaded once per run however many records use it, deduplicated on the download URL before any request. Measured across two production configurations: 614 requests fell to 398, and 144 to 71.
- Media relations are cleaned up when upstream stops supplying an image, for inline and referenced
media alike. Re-importing an unchanged record no longer accumulates
sys_file_referencerows. See Which fields the import owns. - A failed download no longer removes the image it failed to fetch: only
404and410count as the asset being gone. See Which fields the import owns. - Events are imported with their images, both
schema:photoandschema:imagelanding inimages; the previous cap of eight images per event is gone. - Media uploaded directly to a record is imported rather than rejected, for every record type, with its author and licence. The configured API key is sent when fetching from the API host.
- Addresses are imported as inline database records (
tx_) related throughthuecat_ address address_inline, one row per address per language. Each field has its own column, so an address is readable and queryable without decoding a blob, and editors see labelled fields. A record may carry more than one address:schema:addressis read as a list where upstream sends one. Addresses no longer supplied upstream are removed on re-import. - Opening hours are imported as inline database records (
tx_) and exposed display-ready viathuecat_ opening_ hours Place::. See Opening hours.get Computed Opening Hours () - Imports can be run without media via
--. See Import without media.no_ media - Import runs are bounded and report their progress: per-request connect and read timeouts, retries for transient failures, an overall run budget, and progress output at three verbosity levels. Aborted and failed runs write an import log instead of vanishing. See Import tuning and Recovered retries.
- Every import run reports the settings that drove it — storage page, file folder, API domain, the category anchors and the tuning settings — as the first entry of its import log, in the backend module's summary column, and on the console. The API key is never included. See Effective settings of a run.
- Attraction lists, list items and filter forms are cached in the
pagescache group, so everything that already clears page caches clears these too. See Caching. - An event imported without a single date is named in the import log as
eventWithoutDatesat severitywarning. schema:keywordsis imported assys_records in their own tree, for tourist attractions, tourist information and events. Term references, typed ontology literals and editor-typed free text all resolve to a localized title, and the upstream group structure is mirrored as intermediate categories. Keywords stay separate from the categories derived from an object's type: separate anchor, separate relation, separate identifier. Editor renames survive re-import, and a keyword upstream stops supplying loses its relation while the category record remains. See Keywords.category - Imported keywords are usable in the frontend: the attraction list filters by keyword, the filter mask offers them grouped by their parent set, and the selection survives pagination. Selecting several keywords widens the result while other filters continue to narrow it. Which keywords a plugin offers is chosen per plugin and bounded by the site's keyword anchor, so a plugin without a keyword selected behaves exactly as before. See Filtering by keyword.
- A place detail view emits a
keywordsmeta tag built from its related keyword categories. A place with no keyword relations emits no such tag. schema:containedInPlaceis imported in full. Upstream uses the property for whatever contains a record — towns, organisations and other places — while only the first town was kept and the rest discarded. Each reference now lands on a relation chosen by the kind of record it imported into:town,contained_in_organisation, or one ofcontained_in_attraction,contained_in_tourist_informationandcontained_in_parking_facility. One production configuration was dropping 127 such relations per run. A record contained in a kind no relation accepts is still reported asreferenceUnrelatable. The detail view renders the containing organisations and places. See One property, several target tables.- The town filter matches a record carrying several towns, and the search form offers every town its records carry. An attraction in two towns is returned once when both are selected.
thuecat:Trailis imported into its own record type (tx_), with its descriptive texts, route and metrics each in their own column. Way-type segments, current conditions and the start and end locations become inline records; seasons a multi-value field. Trails carry keywords and images like any other record. An attraction contained in a trail resolves to it throughthuecat_ trail contained_in_trail. No frontend output yet — the records exist in the backend.
Fixes
- Imported coordinates keep their full precision. Latitude and longitude are stored as delivered rather than in a decimal column, which TYPO3 rounds to two decimal places on write — enough to move a point by roughly two kilometres. Re-run the import to correct stored coordinates.
- Translated records keep their own address. Address text was read with a helper that understood
only a single
{@language,@value}object, so a record whose upstream address was a list of language variants — the shape used as soon as an address is translated — imported with street, postal code, city, phone, fax and email all empty. The same defect silently affected media copyright years and licences, and accessibility certification values. - Paging through an attraction list no longer shows the wrong page. Every pagination page and filter combination of one plugin previously shared a single page-cache entry.
- The search mask no longer hydrates every record in storage to build its filter options. Measured on 649 attractions: 31.8s to 0.04s, and the tree expansion from 375 queries to a fixed number.
- The search form offers its category options again on a page whose plugin names no storage page. The option tree was built with the default storage restriction, which resolves to page 0 and matches nothing, so the form rendered an empty category list.
- The search form shows the levels below a category's top level. Child categories were looked up by comparing an object relation against uids, which matched nothing, so only the topmost entry of each tree was offered.
- Filtering by a category that groups others returns the records filed under those others. Selecting a group previously matched only records related to the group itself — none, where a vocabulary relates records to the terms it ends in — so every group selection came back empty. Applies to the type categories and to keywords alike.
- A referenced resource that cannot be fetched no longer aborts the whole root URL. Only the
affected reference is dropped, logged per owning record as
referenceSkippedatwarning. - A media entry the import cannot interpret no longer costs the whole root URL.
- An image that cannot be downloaded no longer costs the run its media. Previously one failed download discarded every image downloaded under all other roots.
- Media served behind a redirect is imported. A
301was previously logged asImage could not be downloaded, which cost every asset on a host that redirects — trail images and logos come from one. Redirects are followed up to five hops. Re-run the import to fetch the images earlier runs skipped. See Import. - Staged media is promoted into the target folder even when the run recorded an error elsewhere.
- A record no longer inherits relations from the record imported before it — town, organisation, parking, categories and images alike. Relations already stored from earlier runs are not corrected by this.
- Hiding a storage folder no longer breaks the import that writes into it. Which pages count as
belonging to a site was derived with the frontend's visibility rules, so a hidden folder — or one
past its publication end time — dropped out of the site. Depending on which folder it was, the
run aborted with
… is outside the storagePid's sitefor a page plainly inside the site, or the import stopped recognising what it had already written and built a second copy beside it: a hidden keyword or category folder produced a whole duplicate category tree, splitting records' relations across both. Storage is now what decides, for records, categories and keywords alike; only deleted pages fall out of scope. - A schedule day that cannot seed a date series no longer aborts the event import; the value is
logged as
scheduleDaySkipped, and surplus usable days asscheduleDayDropped. schema:exceptDateis honoured, for every frequency.- An event's stored dates are reconciled with its schedule on re-import, so occurrences that stopped being produced are removed. Only the import's own rows are affected; dates created in the backend survive.
- A failed root URL is identifiable from the import log:
mappingErrorandfetchingErrornow carry the root URL. thuecat:prints a closing message stating whether the run completed, completed with warnings, or failed.importviaconfiguration
Tasks
Nothing
Deprecation
- The json-based media storage is superseded by FAL handling. The json read accessors
Media::,get Main Image () get,Images () get,Extra Images () get,Editorial Images () getandAll Images () Base::triggerget Media () E_USER_DEPRECATED. Use the FAL properties onBaseinstead and re-run the import. See FAL accessors. editorial_imagesis handled as the FAL field it always has been: replace{entity.withmedia. editorial Images} {entity..editorial Images} - The json-based opening hours storage is superseded by inline database records.
Place::,get Opening Hours () get,Merged Opening Hours () getandSpecial Opening Hours () gettriggerMerged Special Opening Hours () E_USER_DEPRECATED, as do the legacyOpening,Hours MergedandOpening Hours Mergedmodels. UseOpening Hour Week Day get/Computed Opening Hours () getand re-run the import. See Computed model.Computed Special Opening Hours () - The json-based address storage is superseded by inline database records.
Place::triggersget Address () E_USER_DEPRECATEDand returns the legacyLegacyvalue object. UseAddress getfor the full set, orAddress Inline () getwhere one address is expected, and re-run the import. TheFirst Address () addresscolumn is kept and still readable so records not yet re-imported keep rendering; it is no longer written and is removed in a later major. The misspelledAddress::isget Latitute () geton the new model; the legacy object keeps the old spelling.Latitude ()