Product text import from agency XLSX workbooks

VITEC Import module: Products tab is now a searchable/sortable product
overview. "Edit Product" opens an XLSX upload with per-field source
mapping (component + cell), old/new preview and checkbox apply via
DataHandler; the mapping and manual matches are persisted and preselect
the next workbook. Category workbooks are detected and rejected.

- new: ProductXlsxReader (PhpSpreadsheet), ProductTextImportController,
  Products/ProductTexts templates, 4 module routes
- related products: per-pair card text in new side table
  tx_vitec_product_related_text (survives MM rewrites), emitted as
  `cardtext` in the product JSON; missing MM relations added add-only
- card copy read from Body Copy (D) with fallback to CTA/Card Copy (E) -
  the workbooks fill either depending on row type
- product detail page <title> now uses seotitle with title fallback
  (provider made singleton and fed from the JSON renderer)
- per-user recent-search badges; last loaded workbook stored per product
  (tx_vitec_product_workbook), Edit Product reopens on it
- composer: add phpoffice/phpspreadsheet ^5.9
- diagnostics: migrations/check_workbook.php
This commit is contained in:
2026-08-21 11:00:45 +02:00
parent 12eb40614b
commit 7bd1161abf
28 changed files with 2594 additions and 39 deletions

View File

@@ -23,6 +23,7 @@
| 1.7 | 20260806 | **Robustness:** the import normalizes legacy filenames on fetch so every imported file matches the version convention (`__NN_A``__NN-A`, `___NN``__NN`, `__NNA``__NN-A`, bare `__NN``__NN-A` as initial revision), and the three download renderers gained a `filepath` fallback in `getDownloadFile()` (FAL → convention → filepath) as a safety net for anything that still escapes it. Extends the B4 duplication (three copies of the fallback) — consolidation target remains a shared fileresolver service (10.2). |
| 1.8 | 20260813 | **Defect fix and interface change (additive).** Content Blocks never carried the Core *Appearance* tab: `layout`, `frame_class` — including the VITEC frame classes — `space_before_class`, `space_after_class`, `sectionIndex` and `linkToTop` were unreachable for editors on all nine blocks. Added centrally for every `vitec_*` type (7.7); the `appearance` envelope is unchanged, its values were merely always default. Side effect: those six columns now also appear raw inside `data` on toplevel blocks (B13), and `appearance.layout` is represented differently on the two envelope paths (B14). `intro-paragraph` gained `background_color` (7.7). `vitec_eventlist` gained the layout `regions`, emitting a `regions` array built from the region categories below parent 104; the event payload is specified for the first time (7.14). B11 and B12 recorded as resolved. |
| 1.9 | 20260815 | **Interface change, partly breaking.** The four list plugins were unified: every one of them now emits an object carrying `layout`, the new `showToolbar` flag and its payload array. `vitec_usecaselist` and `vitec_productlist` previously emitted a **bare array** — front ends reading them have to move one level down (7.3, 7.14, 8). New plugin **`vitec_solutionlist`** (`SolutionListJsonRenderer`, key `solutions`), the counterpart 7.13.2 had been asking for since v1.2. `tx_vitec_domain_model_usecase` gained `detail_page`, emitted as the resolved `detailUrl` in the story card shape and therefore also in the `vitec_modelcard` story branch, which had carried no link at all until now. Noted: `vitec_productlist` had a configurable `layout` that was never serialised, and its vocabulary (`0``3`) differs from the other lists. |
| 1.10 | 20260818 | **Interface change (additive).** `tx_vitec_domain_model_product` gained five fields: `heroimage` (multiple FAL images, detail payload only), the richtext fields `description2`, `capabilities` and `textrelatedproducts`, and `portfolio` (TCA `link`) — emitted as a **resolved URL** through the new `LinkResolver::typolinkUrl()`. The product payloads are specified for the first time (7.15). Links in `contentelement` / `contentelementcta` that point at a **container** now resolve its children (`items`, pagelevel shape) and `background` (7.4). Two record link handlers (`download`, `product`) added to the link browser, resolved serverside per the new Clause 9.12; new middleware `vitec/download-file` streams `/download/file/<uid>` as a forced download (5.3), file lookup consolidated into `DownloadFileResolver` — first step towards the B4 target (10.2). Backendonly: `relatedprodukt` moved from the Misc tab to General. Editorial: the document footer had been stuck at v1.7 since v1.8. |
This document is drafted in the style of, and adopts the terminology conventions of,
ISO/IEC/IEEE 42010 (architecture description), ISO/IEC/IEEE 26514 (information for
@@ -180,7 +181,7 @@ enhancers for products (`tx_vitec_domain_model_product.slug`) and news detail
(`path_segment`).
### 5.3 Frontend middlewares
Two middlewares are registered in `Configuration/RequestMiddlewares.php`, both after
Three middlewares are registered in `Configuration/RequestMiddlewares.php`, all after
`typo3/cms-core/normalized-params-attribute` and before `typo3/cms-frontend/site`
that is, **before page resolution**:
@@ -188,6 +189,7 @@ that is, **before page resolution**:
|---|---|
| `vitec/form-submission` | Answers `POST /api/vitec/form/<formKey>` (Clause 7.8.2). Every other request passes through untouched. |
| `vitec/success-story-path-rewrite` | Rewrites `/success-stories/<slug>` internally to `/success-stories/story/<slug>` when `<slug>` matches a visible Success Story. The page router would otherwise always resolve the public SEO URL to the list page (longest pageslug prefix), so the detail subpage could never answer it. The browser URL is unchanged; a nonmatching slug leaves the request untouched; any exception leaves the request untouched. |
| `vitec/download-file` | Answers `GET /download/file/<uid>`: resolves the download record's file (`DownloadFileResolver`: FAL → Collateral naming convention → `filepath`) and streams it with `Content-Disposition: attachment` (Clause 9.12). Unknown uid, hidden record or missing file fall through to normal page resolution. |
### 5.4 Architectural principles (rationale)
- **P1 — One envelope.** Every content element, regardless of source, is exposed with
@@ -444,6 +446,13 @@ The `colPos` value of each column is fixed by `COLPOS_TO_COLUMN` in
(25/25/25/25), 241/242 (66/33), 251/252 (33/66) — and **shall** be kept in sync with
the container TCA (Annex B5).
**Linked containers (since v1.10).** When a link resolved through
`ContentElementResolver` (e.g. `contentelement` / `contentelementcta`, 7.15) points at
a container, the resolver attaches the container's children under `items` in exactly
the column shape above, plus the resolved `background`. Nested containers recurse
(depthcapped at 5, cyclesafe); children are stripped of containerlevel fields as in
the pagelevel rendering; nested plugins resolve per 9.7.
### 7.5 Success Story (use case) detail payload
Produced by `UsecaseSerializer::serializeDetail()` — the reference implementation of
the centralised pattern (Clause 9.2):
@@ -856,6 +865,36 @@ Normative behaviour:
An event carrying no category belongs to no region and is invisible to this layout while
remaining present in `events`.
### 7.15 Product payloads
#### 7.15.1 Detail payload (`vitec_productshow`, key `product`)
Produced by `ProductShowJsonRenderer::serializeProduct()`. Members:
| Member | Type | Notes |
|---|---|---|
| `uid` | int | |
| `title`, `slug`, `urltitle`, `seotitle`, `seometa`, `keywords`, `structureddata`, `teaser`, `subtitle`, `video`, `shortcutpid` | string | raw column values; `video` is a YouTube clip id |
| `applications`, `capabilities`, `description`, `description2`, `highlights`, `textrelatedproducts` | string | richtext, resolved per 9.11 |
| `hideonapp`, `hideonwebsite`, `hideondatasheets`, `hideonproducts`, `shortcut`, `legacy`, `supportproduct`, `subproduct`, `showdatapath` | bool | |
| `link` | string | `/product/<slug>` |
| `portfolio` | string \| null | resolved URL of the portfolio link (9.12); null = no link |
| `contentelement`, `contentelementcta` | object \| null | linked tt_content element in the envelope shape of 7.2; a linked container carries `items` and `background` (7.4) |
| `categories` | array | `{uid, title, description}` |
| `images` | array | product images with `url`, `srcset` and properties (9.9) |
| `heroimage` | array | hero images, same shape as `images`**detail payload only** |
| `downloads` | array | download records incl. the resolved `file` |
| `ogimage`, `videofile` | object \| null | FAL singletons |
| `relatedprodukt` | array | card shape `{uid, title, slug, subtitle, teaser, description, link, images}`, backend order |
| `jsonLd` | string | readytoemit schema.org `@graph` (6.6), detail only |
#### 7.15.2 List payload (`vitec_productlist`, key `products`)
List envelope per 7.3 (`layout`, `showToolbar`, `products[]`) — since v1.9. Each item
carries the member set of 7.15.1 **except** `heroimage` and `jsonLd`. `heroimage` is
deliberately detailonly: one further FAL query per product would buy data a list
rarely shows. The `layout` vocabulary remains `0``3` (see the v1.9 note).
---
## 8 Contenttype catalogue
@@ -1016,6 +1055,23 @@ Fields that merely *look* like richtext are out of scope: FAL metadata
(`sys_file_reference.description`), `sys_category.description` and `sys_file` metadata
are plain text and **shall not** be passed through the resolver.
### 9.12 Link resolution and record links
JSON output **shall never** carry an unresolved TYPO3 link (`t3://…`). Every link
field is resolved serverside — `LinkResolver::pageUrl()` for page uids,
`LinkResolver::typolinkUrl()` for full typolink parameters — under the shared
contract **null means "no link"**: a hidden or deleted target resolves to null,
never to a broken URL.
The link browser offers two record link handlers (`TCEMAIN.linkHandler`,
`Configuration/page.tsconfig`), stored as `t3://record?identifier=<key>&uid=<n>`
and resolved by `config.recordLinks` in `setup.typoscript`:
| Identifier | Resolves to | Notes |
|---|---|---|
| `product` | `/product/<slug>` | same convention as the `link` member (7.15); assumes the product detail page (uid 10) answers `/product` |
| `download` | `/download/file/<uid>` | forceddownload endpoint (5.3), file lookup via `DownloadFileResolver` |
---
## 10 Maintainability and upgradesafety (ISO 25010)
@@ -1208,4 +1264,4 @@ remediation.
- Header convention — the uniform header section across CEs, plugins and containers.
- `Configuration/Sets/Vitecset/setup.typoscript` — the single TypoScript entry point.
*End of document EVOVITECHL001 v1.7.*
*End of document EVOVITECHL001 v1.10.*