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

@@ -103,7 +103,7 @@ as the reference).
| Products | `vitec_productlist` / `vitec_productshow` | `ProductList/ProductShowJsonRenderer` | `products` / `product` |
| Success Stories | `vitec_usecaselist` / `vitec_usecaseshow` | `UsecaseList/ShowJsonRenderer``UsecaseSerializer` | `usecases` / `usecase` |
| Markets | `vitec_marketlist` / `vitec_marketshow` | `MarketList/MarketShowJsonRenderer` | `markets` / `market` |
| Solutions | `vitec_solutionshow` | `SolutionShowJsonRenderer` | `solution` |
| Solutions | `vitec_solutionlist` / `vitec_solutionshow` | `SolutionList/SolutionShowJsonRenderer` | `solutions` / `solution` |
| Downloads | `vitec_downloadcard` / `vitec_downloadcardcollection` | `Downloadcard*JsonRenderer` | `downloadcard` / `downloadcardcollection` |
| Datasheets | `vitec_datasheets` | `DatasheetsJsonRenderer` | `datasheets` |
| Events | `vitec_eventlist` | `EventlistJsonRenderer` | `eventlist` |
@@ -113,6 +113,10 @@ as the reference).
| Forms | `vitec_contactform` / `vitec_demoform` / `vitec_helpdeskform` | `FormsJsonRenderer` | `form` |
| News | `news_pi1` (+ 8 variants) | `NewsJsonRenderer` | `news` |
**Events** (`vitec_eventlist`) offer four layouts including `regions`, which groups
upcoming events by region category — each region tile carries its next event and
that event's image.
**Cards** (`vitec_modelcard`) are one plugin for four model types — the FlexForm picks
`product`, `story`, `market` or `solution` plus a record, and every card field comes
from that record. Image resolution is delegated to `UsecaseSerializer::image()`.
@@ -134,6 +138,8 @@ Declarative content elements (`friendsoftypo3/content-blocks`), serialised to JS
| `video` | YouTube or uploaded video with poster |
| `faq` | Accordion; also feeds the `FAQPage` structured data |
| `columns` | Twocolumn layout (50/50 · 66/33 · 33/66) with peritem content |
| `quotation` | Quote / testimonial (shared header section, header optional) |
| `featured-content` | Featured teaser with image, colour overlay and CTA |
## Layout containers
@@ -179,9 +185,11 @@ Beyond `content[]`, every page response carries:
| `favicons` | `FaviconsJsonRenderer` — readytorender `<link>` descriptors plus `themeColor` |
| `jsonLd` | `PageJsonLdRenderer` (see below) |
Two frontend middlewares run before page resolution: `vitec/form-submission` (the form
endpoint) and `vitec/success-story-path-rewrite`, which lets the public SEO URL
`/success-stories/<slug>` resolve to the detail subpage without changing the browser URL.
Three frontend middlewares run before page resolution: `vitec/form-submission` (the form
endpoint); `vitec/success-story-path-rewrite`, which lets the public SEO URL
`/success-stories/<slug>` resolve to the detail subpage without changing the browser URL;
and `vitec/download-file`, which streams `/download/file/<uid>` as a forced download —
the target of the link browser's Download record links.
## Structured data (JSONLD)
@@ -194,17 +202,31 @@ endpoint) and `vitec/success-story-path-rewrite`, which lets the public SEO URL
**Backend module "VITEC Import"** (Web menu): CSV import per domain model
(Market, Solution, Product) with a persistable column mapper and a unified
review list (new / update / unchanged / db-only) — what gets written is the
editable per-row payload, applied through DataHandler. A fourth tab
editable per-row payload, applied through DataHandler. Rows whose CSV name
differs from the TYPO3 record can be linked by hand (persistable record
aliases), so they keep matching as updates on every future delivery. A fourth tab
**SEO Research** stores each delivery of the recurring keyword-research CSV,
diffs it against the previous one and checks the CSV structure against the
page tree and the domain records.
**Backend module "WordPress Import"** (Web menu, behind the CSV import): pulls
posts from a WordPress REST API (VITEC blog and the Datapath site) into news
records — checkbox selection, featured and inline images are fetched and
localized so they survive the go-live.
**Link browser record links**: two extra tabs (Download, Product) let editors link
download and product records wherever links are offered. The JSON always carries the
resolved URL — products point at the detail view (`/product/<slug>`), downloads at
the forced-download endpoint.
| CLI command | Purpose |
|---|---|
| `vitec:import-success-stories` | One-time migration of the old-site success stories |
| `vitec:import-downloads` | Import old-site downloads (Collateral only, idempotent, filename normalization) |
| `vitec:create-markets` | Create market records the SEO structure check reports missing, incl. sys_category assignment |
| `vitec:market-dummy-image` | Assign the shared placeholder image to markets without an image |
| `vitec:migrate-newspages` | Rewire impexp-imported old-site news pages (FLUX `colPos` nesting, `internalurl`) via `tx_impexp_origuid` |
| `vitec:import-news` | Import old-site news records from `migrations/news_export.json` |
All commands support `--dry-run` and are safe to re-run.
@@ -278,7 +300,7 @@ packages/vitec/
│ ├── Service/ # Serializers, ContentElementResolver, StructuredDataService
│ ├── DataProcessing/ # ContainerChildrenProcessor (container → items)
│ ├── Forms/ # FormDefinitions + Delivery/ (email, salesforce)
│ ├── Middleware/ # form endpoint, success-story path rewrite
│ ├── Middleware/ # form endpoint, success-story rewrite, download streaming
│ ├── Domain/Model|Repository/
│ ├── Controller/ # Extbase controllers (non-headless / backend)
│ └── Backend/ · View/ · Hook/ · EventListener/ · Tca/ · Preview/