Files
VITEC-website/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript
Oliver Rasche 5aad13e635 Keep hidden headers (header_layout 100) out of the JSON
Applied on all three render paths: stdWrap.if rule on
lib.contentElementWithHeader (before the plugin copies), the same rule in
the container element definitions, and a HiddenHeaderProcessor behind the
nb content-blocks data processor. headerLayout itself stays in the payload
so the front end can tell why the header is empty.
2026-09-04 13:21:50 +02:00

622 lines
20 KiB
Plaintext
Executable File

config {
# EXT:solr indexes pages only when indexing is explicitly enabled
index_enable = 1
pageTitleProviders {
vitec {
provider = Evomedien\Vitec\PageTitle\ProductPageTitleProvider
before = record
before = seo
}
vitecUsecase {
provider = Evomedien\Vitec\PageTitle\UsecasePageTitleProvider
before = record
before = seo
}
}
}
@import 'EXT:headless/Configuration/TypoScript/ContentElement/*.typoscript'
# =============================================================================
# header_layout 100 = "Hidden" (decision 2026-09-04): such headers stay OUT of
# the JSON - header, subheader and headerLink render empty. headerLayout (100)
# stays in the payload so the front end could still tell why. Placed BEFORE
# the `< lib.contentElementWithHeader` copies below, which snapshot this lib.
# The content-blocks `data` path gets the same rule via HiddenHeaderProcessor.
# =============================================================================
lib.contentElementWithHeader.fields.content.fields {
header.stdWrap.if {
value = 100
equals.field = header_layout
negate = 1
}
subheader.stdWrap.if {
value = 100
equals.field = header_layout
negate = 1
}
headerLink.stdWrap.if {
value = 100
equals.field = header_layout
negate = 1
}
}
lib.contentBlock.fields.data.dataProcessing.20 = Evomedien\Vitec\DataProcessing\HiddenHeaderProcessor
# =============================================================================
# Headless list-plugin JSON renderers (TYPO3 v14: each plugin is its own CType)
#
# For every VITEC plugin the renderer's USER output is exposed as a sub-field
# of the standard content-element envelope (`content.<key>`), inheriting all
# default fields (id, type, colPos, categories, appearance, …) from
# lib.contentElement provided by friendsoftypo3/headless.
# =============================================================================
tt_content {
vitec_productlist < lib.contentElementWithHeader
vitec_productlist {
fields {
content {
fields {
products = USER
products.userFunc = Evomedien\Vitec\UserFunc\ProductListJsonRenderer->render
}
}
}
}
# EXT:solr search results as JSON - the search endpoint for the React frontend
solr_pi_results < lib.contentElementWithHeader
solr_pi_results {
fields {
content {
fields {
search = USER
search.userFunc = Evomedien\Vitec\UserFunc\SearchJsonRenderer->render
}
}
}
}
news_pi1 < lib.contentElementWithHeader
news_pi1 {
fields {
content {
fields {
news = USER
news.userFunc = Evomedien\Vitec\UserFunc\NewsJsonRenderer->render
}
}
}
}
news_newsliststicky < tt_content.news_pi1
news_newsselectedlist < tt_content.news_pi1
news_newsdetail < tt_content.news_pi1
news_newsdatemenu < tt_content.news_pi1
news_categorylist < tt_content.news_pi1
news_newssearchform < tt_content.news_pi1
news_newssearchresult < tt_content.news_pi1
news_taglist < tt_content.news_pi1
vitec_productshow < lib.contentElementWithHeader
vitec_productshow {
fields {
content {
fields {
product = USER
product.userFunc = Evomedien\Vitec\UserFunc\ProductShowJsonRenderer->render
}
}
}
}
vitec_usecaselist < lib.contentElementWithHeader
vitec_usecaselist {
fields {
content {
fields {
usecases = USER
usecases.userFunc = Evomedien\Vitec\UserFunc\UsecaseListJsonRenderer->render
}
}
}
}
vitec_usecaseshow < lib.contentElementWithHeader
vitec_usecaseshow {
fields {
content {
fields {
usecase = USER
usecase.userFunc = Evomedien\Vitec\UserFunc\UsecaseShowJsonRenderer->render
}
}
}
}
vitec_marketshow < lib.contentElementWithHeader
vitec_marketshow {
fields {
content {
fields {
market = USER
market.userFunc = Evomedien\Vitec\UserFunc\MarketShowJsonRenderer->render
}
}
}
}
vitec_marketlist < lib.contentElementWithHeader
vitec_marketlist {
fields {
content {
fields {
markets = USER
markets.userFunc = Evomedien\Vitec\UserFunc\MarketListJsonRenderer->render
}
}
}
}
vitec_solutionlist < lib.contentElementWithHeader
vitec_solutionlist {
fields {
content {
fields {
solutions = USER
solutions.userFunc = Evomedien\Vitec\UserFunc\SolutionListJsonRenderer->render
}
}
}
}
vitec_solutionshow < lib.contentElementWithHeader
vitec_solutionshow {
fields {
content {
fields {
solution = USER
solution.userFunc = Evomedien\Vitec\UserFunc\SolutionShowJsonRenderer->render
}
}
}
}
vitec_downloadcard < lib.contentElementWithHeader
vitec_downloadcard {
fields {
content {
fields {
downloadcard = USER
downloadcard.userFunc = Evomedien\Vitec\UserFunc\DownloadcardJsonRenderer->render
}
}
}
}
vitec_downloadcardcollection < lib.contentElementWithHeader
vitec_downloadcardcollection {
fields {
content {
fields {
downloadcardcollection = USER
downloadcardcollection.userFunc = Evomedien\Vitec\UserFunc\DownloadcardcollectionJsonRenderer->render
}
}
}
}
vitec_datasheets < lib.contentElementWithHeader
vitec_datasheets {
fields {
content {
fields {
datasheets = USER
datasheets.userFunc = Evomedien\Vitec\UserFunc\DatasheetsJsonRenderer->render
}
}
}
}
vitec_eventlist =< lib.contentElementWithHeader
vitec_eventlist {
fields {
content {
fields {
eventlist = USER
eventlist.userFunc = Evomedien\Vitec\UserFunc\EventlistJsonRenderer->render
}
}
}
}
vitec_contactform < lib.contentElementWithHeader
vitec_contactform {
fields {
content {
fields {
form = USER
form.userFunc = Evomedien\Vitec\UserFunc\FormsJsonRenderer->render
}
}
}
}
vitec_demoform < tt_content.vitec_contactform
vitec_helpdeskform < tt_content.vitec_contactform
vitec_modelcard < lib.contentElementWithHeader
vitec_modelcard {
fields {
content {
fields {
card = USER
card.userFunc = Evomedien\Vitec\UserFunc\ModelcardJsonRenderer->render
}
}
}
}
vitec_customerlogos < lib.contentElementWithHeader
vitec_customerlogos {
fields {
content {
fields {
customerlogos = USER
customerlogos.userFunc = Evomedien\Vitec\UserFunc\CustomerlogosJsonRenderer->render
}
}
}
}
vitec_locationlist < lib.contentElementWithHeader
vitec_locationlist {
fields {
content {
fields {
locations = USER
locations.userFunc = Evomedien\Vitec\UserFunc\LocationsJsonRenderer->render
}
}
}
}
}
# Include container (layout) rendering definitions
@import 'EXT:vitec/Configuration/TypoScript/Headless/vitec_containers.typoscript'
# Include menu (navigation) JSON definitions
@import 'EXT:vitec/Configuration/TypoScript/Headless/vitec_menus.typoscript'
# =============================================================================
# Page-level structured data (schema.org JSON-LD @graph)
#
# Organization + WebSite + BreadcrumbList + NewsArticle, auto-generated server
# side from page/site data. The headless frontend wraps the returned string in
# <script type="application/ld+json"> inside the page <head>.
# =============================================================================
page.10.fields.jsonLd = USER
page.10.fields.jsonLd.userFunc = Evomedien\Vitec\UserFunc\PageJsonLdRenderer->render
############################################################
# Frontend resolution for the record links from the link
# browser (page.tsconfig TCEMAIN.linkHandler):
# download -> forced download endpoint (DownloadFileMiddleware)
# product -> detail view, same /product/<slug> convention as
# the `link` field in the product JSON (page 10)
# A hidden or deleted target makes the typolink fail -> null.
############################################################
config.recordLinks {
download {
forceLink = 1
typolink {
parameter.cObject = TEXT
parameter.cObject.dataWrap = /download/file/{field:uid}
}
}
product {
forceLink = 1
typolink {
parameter.cObject = TEXT
parameter.cObject.dataWrap = /product/{field:slug}
}
}
}
# Highlighted teaser fragments for the JSON search results (SearchJsonRenderer)
plugin.tx_solr.search.results.resultsHighlighting = 1
plugin.tx_solr.search.results.resultsHighlighting.wrap = <mark>|</mark>
# Headless has no TYPO3SEARCH_begin/end markers in its JSON output, so the
# default page content extraction indexes an EMPTY content field. Build the
# content from the page's tt_content rows instead: header, subheader, bodytext
# cover core CTypes and the Content Blocks (their text fields reuse bodytext),
# vitec_quote is the only Content Blocks text column of its own. SOLR_CONTENT
# strips markup before indexing. Collection children (FAQ items, cards) live in
# their own tables and are not covered yet.
plugin.tx_solr.index.queue.pages.fields {
content = SOLR_CONTENT
content {
cObject = COA
cObject {
10 = CONTENT
10 {
table = tt_content
select {
orderBy = sorting
}
renderObj = COA
renderObj {
10 = TEXT
10.field = header
10.noTrimWrap = || |
20 = TEXT
20.field = subheader
20.noTrimWrap = || |
30 = TEXT
30.field = bodytext
30.noTrimWrap = || |
40 = TEXT
40.field = vitec_quote
40.noTrimWrap = || |
}
}
}
}
}
# Log every executed search (keywords, hit count) into tx_solr_statistics -
# feeds the Search Phrase Statistics backend module, most useful for the
# zero-hit list. anonymizeIP masks the last two octets before storing (GDPR).
plugin.tx_solr.statistics = 1
plugin.tx_solr.statistics.anonymizeIP = 2
# =============================================================================
# Record indexing: custom domain models as their own search documents.
# The configuration name becomes the document `type` in the search JSON, so the
# frontend can facet/label results. Every type needs a resolvable `url` -
# a hit without a link is useless, hence the where-clauses below.
# NOT indexed (yet): solutions (no slug, no detail_page - nothing to link to),
# downloads/events/locations (no public detail pages), Datapath news (pid 442,
# import awaiting an editorial decision).
# =============================================================================
plugin.tx_solr.index.queue {
products = 1
products {
type = tx_vitec_domain_model_product
fields {
title = title
content = SOLR_CONTENT
content {
cObject = COA
cObject {
10 = TEXT
10.field = subtitle
10.noTrimWrap = || |
20 = TEXT
20.field = teaser
20.noTrimWrap = || |
30 = TEXT
30.field = description
30.noTrimWrap = || |
40 = TEXT
40.field = description2
40.noTrimWrap = || |
50 = TEXT
50.field = capabilities
50.noTrimWrap = || |
60 = TEXT
60.field = highlights
60.noTrimWrap = || |
70 = TEXT
70.field = applications
70.noTrimWrap = || |
}
}
url = TEXT
url {
typolink {
parameter = 10
additionalParams = &tx_vitec_productshow[product]={field:uid}
additionalParams.insertData = 1
returnLast = url
}
}
}
}
markets = 1
markets {
type = tx_vitec_domain_model_market
# only markets with an editorially assigned detail page are linkable;
# grows automatically as editors fill the field (4 of 34 right now)
additionalWhereClause = detail_page > 0
fields {
title = title
content = SOLR_CONTENT
content {
cObject = COA
cObject {
10 = TEXT
10.field = subtitle
10.noTrimWrap = || |
20 = TEXT
20.field = teaser
20.noTrimWrap = || |
30 = TEXT
30.field = description
30.noTrimWrap = || |
}
}
url = TEXT
url {
typolink {
parameter.field = detail_page
returnLast = url
}
}
}
}
usecases = 1
usecases {
type = tx_vitec_domain_model_usecase
fields {
title = title
content = SOLR_CONTENT
content {
cObject = COA
cObject {
10 = TEXT
10.field = subtitle
10.noTrimWrap = || |
20 = TEXT
20.field = teaser
20.noTrimWrap = || |
}
}
url = TEXT
url {
typolink {
# page 26 carries the vitec_usecaseshow plugin; the route
# enhancer turns this into /success-stories/story/<slug>
parameter = 26
additionalParams = &tx_vitec_usecaseshow[usecase]={field:uid}
additionalParams.insertData = 1
returnLast = url
}
}
}
}
news = 1
news {
type = tx_news_domain_model_news
# type 1 ("page as news") points at pages that are already indexed as
# pages - indexing them again would duplicate every one of them
additionalWhereClause = pid = 11 AND type = '0'
fields {
title = title
content = SOLR_CONTENT
content {
cObject = COA
cObject {
10 = TEXT
10.field = teaser
10.noTrimWrap = || |
20 = TEXT
20.field = bodytext
20.noTrimWrap = || |
}
}
url = TEXT
url {
typolink {
parameter = 21
additionalParams = &tx_news_pi1[controller]=News&tx_news_pi1[action]=detail&tx_news_pi1[news]={field:uid}
additionalParams.insertData = 1
returnLast = url
}
}
}
}
}
# Products are what visitors of a product site are usually looking for -
# without this boost, decades of news articles dominate the first result page
# for product-ish terms like "encoder". Stories get a smaller nudge.
plugin.tx_solr.search.query.boostQuery (
(type:tx_vitec_domain_model_product)^10.0 (type:tx_vitec_domain_model_usecase)^2.0
)
# Downloads have no detail page but a public file endpoint served by
# DownloadFileMiddleware. The where-clause keeps private / website-hidden
# downloads out of the public index (the middleware itself does not enforce
# private_download - the index must not advertise those files).
plugin.tx_solr.index.queue {
downloads = 1
downloads {
type = tx_vitec_domain_model_download
additionalWhereClause = private_download = 0 AND hideonwebsite = 0
fields {
title = title
content = SOLR_CONTENT
content {
cObject = COA
cObject {
10 = TEXT
10.field = teaser
10.noTrimWrap = || |
20 = TEXT
20.field = keywords
20.noTrimWrap = || |
30 = TEXT
30.field = description
30.noTrimWrap = || |
}
}
url = TEXT
url.dataWrap = /download/{field:slug}
}
}
}
# Type facet for the search JSON: counts per document type, serialized by
# SearchJsonRenderer as content.search.facets.type. keepAll* keeps the full
# counts visible while one type is selected - otherwise the tabs would
# collapse to the active one.
plugin.tx_solr.search.faceting = 1
plugin.tx_solr.search.faceting {
keepAllFacetsOnSelection = 1
facets {
type {
label = Type
field = type
keepAllOptionsOnSelection = 1
}
}
}
# =============================================================================
# Autocomplete endpoint: GET /search?type=7384&tx_solr[queryString]=<input>
# Lean JSON page type (no page envelope) for type-ahead in the React search
# field. Response: { suggestions: {term: count}, suggestion, documents[],
# didSecondSearch }.
# 10 is deliberately USER_INT, not USER as in the shipped example: the example
# predates TYPO3 v14 (config.no_cache is gone), and a cached USER would pin
# the first query's suggestions for every later request.
# =============================================================================
tx_solr_suggest = PAGE
tx_solr_suggest {
typeNum = 7384
config {
disableAllHeaderCode = 1
additionalHeaders.10.header = Content-Type: application/json; charset=utf-8
}
10 = USER_INT
10 {
userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
extensionName = Solr
pluginName = pi_suggest
vendorName = ApacheSolrForTypo3
controller = Suggest
action = suggest
}
}
plugin.tx_solr.suggest = 1
plugin.tx_solr.suggest {
numberOfSuggestions = 8
suggestField = spell
# term suggestions only: the built-in top-results carry the raw document
# type and the full indexed content - too fat and off-contract for type-ahead
showTopResults = 0
}