Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55f7f82a42 | ||
|
|
c90649fc1e |
No files matched your search
Generated
+321
-287
File diff suppressed because it is too large.
Load diff
@@ -6,7 +6,6 @@ dependencies:
|
|||||||
- typo3/form
|
- typo3/form
|
||||||
- typo3/seo-sitemap
|
- typo3/seo-sitemap
|
||||||
- friendsoftypo3/headless
|
- friendsoftypo3/headless
|
||||||
- friendsoftypo3/headless-mixed
|
|
||||||
- nb-headless-content-blocks/headless-content-blocks
|
- nb-headless-content-blocks/headless-content-blocks
|
||||||
- evomedien/vitecset
|
- evomedien/vitecset
|
||||||
- vitec/content-blocks-bundle
|
- vitec/content-blocks-bundle
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ my.example.setting: Test
|
|||||||
menu.footer.pageUids: '1,2,3'
|
menu.footer.pageUids: '1,2,3'
|
||||||
menu.meta.pageUids: '5'
|
menu.meta.pageUids: '5'
|
||||||
megamenu.contentUid: 10366
|
megamenu.contentUid: 10366
|
||||||
|
vitec.storyDetailPid: 26
|
||||||
+9
-3
@@ -16,9 +16,15 @@ set -u
|
|||||||
cd "$(dirname "$0")" || exit 1
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
|
||||||
echo "[perms] Project root: $(pwd)"
|
echo "[perms] Project root: $(pwd)"
|
||||||
echo "[perms] Setting files 644 / directories 755 under Resources/Public + _assets ..."
|
# public/fileadmin as a whole, not just icons: every editorial upload and every
|
||||||
find public/_assets public/fileadmin/icons packages/vitec/Resources/Public -type f -exec chmod 644 {} \; 2>/dev/null
|
# importer output lands there (success_stories_import, blog-import, downloads,
|
||||||
find public/_assets public/fileadmin/icons packages/vitec/Resources/Public -type d -exec chmod 755 {} \; 2>/dev/null
|
# user_upload/products ...) and hits the same 403 otherwise. Found 2026-09-11,
|
||||||
|
# when the imported success-story images stayed invisible in the megamenu.
|
||||||
|
TARGETS="public/_assets public/fileadmin packages/vitec/Resources/Public"
|
||||||
|
|
||||||
|
echo "[perms] Setting files 644 / directories 755 under $TARGETS ..."
|
||||||
|
find $TARGETS -type f -exec chmod 644 {} \; 2>/dev/null
|
||||||
|
find $TARGETS -type d -exec chmod 755 {} \; 2>/dev/null
|
||||||
|
|
||||||
echo "[perms] Flushing TYPO3 caches ..."
|
echo "[perms] Flushing TYPO3 caches ..."
|
||||||
if [ -x vendor/bin/typo3 ]; then
|
if [ -x vendor/bin/typo3 ]; then
|
||||||
|
|||||||
@@ -204,6 +204,14 @@ class ProductListJsonRenderer
|
|||||||
'layout' => (string)($settings['layout'] ?? '0'),
|
'layout' => (string)($settings['layout'] ?? '0'),
|
||||||
'showToolbar' => (bool)($settings['showtoolbar'] ?? false),
|
'showToolbar' => (bool)($settings['showtoolbar'] ?? false),
|
||||||
'products' => $productsData,
|
'products' => $productsData,
|
||||||
|
// Success stories the editor attached to these categories
|
||||||
|
// (sys_category, Options tab). They ride along with the list
|
||||||
|
// so the front end can hide them with the same category
|
||||||
|
// filter it applies to the products.
|
||||||
|
'successStories' => $this->successStoriesFor(
|
||||||
|
$allProducts ? [] : $categoryUids,
|
||||||
|
(int)($settings['storysinglepid'] ?? 0)
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($debugMode) {
|
if ($debugMode) {
|
||||||
@@ -222,6 +230,141 @@ class ProductListJsonRenderer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success stories attached to the given categories, in the order the
|
||||||
|
* categories were resolved and, inside one category, in the order the
|
||||||
|
* editor arranged them. A story linked from several of the categories
|
||||||
|
* appears once and carries all of them in `forCategories`, which is what
|
||||||
|
* the front end filters on: the product finder writes ?cat= / ?subcat=,
|
||||||
|
* and a card stays visible while one of its categories is selected.
|
||||||
|
*
|
||||||
|
* Cards use the shared story shape (UsecaseSerializer), so the front end
|
||||||
|
* can render them with the same component as every other story list.
|
||||||
|
* `detailUrl` follows the same rule as the story list: the story's own
|
||||||
|
* detail page wins, otherwise the Single PID configured on this element
|
||||||
|
* plus the slug - and null when neither is available.
|
||||||
|
*
|
||||||
|
* Returned in the list envelope of 7.3 - `layout` plus the array - so the
|
||||||
|
* front end treats these cards like every other story list. The layout is
|
||||||
|
* an editorial setting on the category, next to the selection itself.
|
||||||
|
*
|
||||||
|
* @param int[] $categoryUids
|
||||||
|
* @return array{layout:string,stories:array<int,array<string,mixed>>}
|
||||||
|
*/
|
||||||
|
private function successStoriesFor(array $categoryUids, int $singlePid): array
|
||||||
|
{
|
||||||
|
if ($categoryUids === []) {
|
||||||
|
return ['layout' => 'grid', 'stories' => []];
|
||||||
|
}
|
||||||
|
|
||||||
|
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
|
||||||
|
->getQueryBuilderForTable('sys_category');
|
||||||
|
$rows = $queryBuilder->select('uid', 'tx_vitec_success_stories', 'tx_vitec_success_stories_layout')->from('sys_category')
|
||||||
|
->where(
|
||||||
|
$queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($categoryUids, Connection::PARAM_INT_ARRAY)),
|
||||||
|
$queryBuilder->expr()->eq('deleted', 0),
|
||||||
|
$queryBuilder->expr()->eq('hidden', 0)
|
||||||
|
)->executeQuery()->fetchAllAssociative();
|
||||||
|
|
||||||
|
$listByCategory = [];
|
||||||
|
$layoutByCategory = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$listByCategory[(int)$row['uid']] = (string)($row['tx_vitec_success_stories'] ?? '');
|
||||||
|
$layoutByCategory[(int)$row['uid']] = (string)($row['tx_vitec_success_stories_layout'] ?? '') ?: 'grid';
|
||||||
|
}
|
||||||
|
|
||||||
|
$order = [];
|
||||||
|
$forCategories = [];
|
||||||
|
$layout = 'grid';
|
||||||
|
foreach ($categoryUids as $categoryUid) {
|
||||||
|
foreach (explode(',', $listByCategory[(int)$categoryUid] ?? '') as $raw) {
|
||||||
|
$storyUid = (int)trim($raw);
|
||||||
|
if ($storyUid <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($order === []) {
|
||||||
|
// The first category that actually contributes stories sets
|
||||||
|
// the layout - that is the top-most one the element is
|
||||||
|
// configured with, which is where an editor expects the
|
||||||
|
// setting to live.
|
||||||
|
$layout = $layoutByCategory[(int)$categoryUid] ?? 'grid';
|
||||||
|
}
|
||||||
|
if (!in_array($storyUid, $order, true)) {
|
||||||
|
$order[] = $storyUid;
|
||||||
|
}
|
||||||
|
$forCategories[$storyUid][] = (int)$categoryUid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($order === []) {
|
||||||
|
return ['layout' => 'grid', 'stories' => []];
|
||||||
|
}
|
||||||
|
|
||||||
|
$storyQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
|
||||||
|
->getQueryBuilderForTable('tx_vitec_domain_model_usecase');
|
||||||
|
$storyRows = $storyQueryBuilder->select('*')->from('tx_vitec_domain_model_usecase')
|
||||||
|
->where(
|
||||||
|
$storyQueryBuilder->expr()->in('uid', $storyQueryBuilder->createNamedParameter($order, Connection::PARAM_INT_ARRAY)),
|
||||||
|
$storyQueryBuilder->expr()->eq('deleted', 0),
|
||||||
|
$storyQueryBuilder->expr()->eq('hidden', 0)
|
||||||
|
)->executeQuery()->fetchAllAssociative();
|
||||||
|
|
||||||
|
$byUid = [];
|
||||||
|
foreach ($storyRows as $storyRow) {
|
||||||
|
$byUid[(int)$storyRow['uid']] = $storyRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same base as the story list: the detail page's PARENT path, because
|
||||||
|
// SuccessStoryPathRewrite maps /success-stories/<slug> onto the detail
|
||||||
|
// subpage at request time. Falls back to the site setting
|
||||||
|
// `vitec.storyDetailPid`, so the editor does not have to configure the
|
||||||
|
// page on every single product list.
|
||||||
|
if ($singlePid <= 0) {
|
||||||
|
$singlePid = $this->storyDetailPidFromSite();
|
||||||
|
}
|
||||||
|
$detailBase = '';
|
||||||
|
if ($singlePid > 0) {
|
||||||
|
$detailPath = rtrim(\Evomedien\Vitec\Service\LinkResolver::pageUrl($singlePid) ?? '', '/');
|
||||||
|
$parent = str_contains($detailPath, '/') ? substr($detailPath, 0, (int)strrpos($detailPath, '/')) : '';
|
||||||
|
$detailBase = $parent !== '' ? $parent : $detailPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
$serializer = GeneralUtility::makeInstance(\Evomedien\Vitec\Service\UsecaseSerializer::class);
|
||||||
|
$stories = [];
|
||||||
|
foreach ($order as $storyUid) {
|
||||||
|
if (!isset($byUid[$storyUid])) {
|
||||||
|
continue; // hidden or deleted meanwhile - simply drops out
|
||||||
|
}
|
||||||
|
$item = $serializer->serializeListItem($byUid[$storyUid]);
|
||||||
|
if (($item['detailUrl'] ?? null) === null && $detailBase !== '' && ($item['slug'] ?? '') !== '') {
|
||||||
|
$item['detailUrl'] = $detailBase . '/' . ltrim((string)$item['slug'], '/');
|
||||||
|
}
|
||||||
|
// NOT the story's own topic categories (those stay in `categories`)
|
||||||
|
// but the product categories it was attached to.
|
||||||
|
$item['forCategories'] = array_values(array_unique($forCategories[$storyUid]));
|
||||||
|
$stories[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['layout' => $layout, 'stories' => $stories];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Site-wide fallback for the success story detail page, 0 when unset.
|
||||||
|
* Read from the site settings rather than hard-coded, so the page can be
|
||||||
|
* moved without touching code.
|
||||||
|
*/
|
||||||
|
private function storyDetailPidFromSite(): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$site = ($GLOBALS['TYPO3_REQUEST'] ?? null)?->getAttribute('site');
|
||||||
|
if ($site === null || !method_exists($site, 'getSettings')) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (int)$site->getSettings()->get('vitec.storyDetailPid', 0);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The given category uids plus every descendant category uid, DEPTH first
|
* The given category uids plus every descendant category uid, DEPTH first
|
||||||
* over sys_category.parent with siblings in sys_category.sorting order -
|
* over sys_category.parent with siblings in sys_category.sorting order -
|
||||||
|
|||||||
@@ -100,6 +100,16 @@
|
|||||||
<default>0</default>
|
<default>0</default>
|
||||||
</config>
|
</config>
|
||||||
</settings.showtoolbar>
|
</settings.showtoolbar>
|
||||||
|
<settings.storysinglepid>
|
||||||
|
<label>Success stories: detail page</label>
|
||||||
|
<description>Needed only so the success-story cards below this list get a link. Pick the same story detail page the Success Stories list uses. Leave empty and the cards render without a link.</description>
|
||||||
|
<config>
|
||||||
|
<type>group</type>
|
||||||
|
<allowed>pages</allowed>
|
||||||
|
<size>1</size>
|
||||||
|
<maxitems>1</maxitems>
|
||||||
|
</config>
|
||||||
|
</settings.storysinglepid>
|
||||||
</el>
|
</el>
|
||||||
</ROOT>
|
</ROOT>
|
||||||
</sDEF>
|
</sDEF>
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ settings:
|
|||||||
type: bool
|
type: bool
|
||||||
default: false
|
default: false
|
||||||
|
|
||||||
|
vitec.storyDetailPid:
|
||||||
|
label: 'Success story detail page'
|
||||||
|
description: 'The page that renders a single success story. Used to build story links where no page is configured on the element itself - the story URL is that page''s parent path plus the story slug, which is what SuccessStoryPathRewrite expects. 0 = no fallback.'
|
||||||
|
category: VITEC
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
|
||||||
menu.footer.pageUids:
|
menu.footer.pageUids:
|
||||||
label: 'Footer menu — page UIDs'
|
label: 'Footer menu — page UIDs'
|
||||||
description: 'Comma-separated list of page UIDs to display in the footer menu (in order).'
|
description: 'Comma-separated list of page UIDs to display in the footer menu (in order).'
|
||||||
|
|||||||
@@ -32,6 +32,47 @@ $customSysCategoryColumns = [
|
|||||||
'eval' => 'trim',
|
'eval' => 'trim',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
// Success stories shown below the product list of this category. Lives on
|
||||||
|
// the category rather than on the plugin so one editorial decision serves
|
||||||
|
// every list that renders the category (decision 2026-09-11).
|
||||||
|
'tx_vitec_success_stories' => [
|
||||||
|
'exclude' => true,
|
||||||
|
'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:sys_category.tx_vitec_success_stories',
|
||||||
|
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:sys_category.tx_vitec_success_stories.description',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'select',
|
||||||
|
'renderType' => 'selectMultipleSideBySide',
|
||||||
|
'foreign_table' => 'tx_vitec_domain_model_usecase',
|
||||||
|
// No ORDER BY in foreign_table_where: TYPO3 appends its own
|
||||||
|
// conditions after this fragment, which turns an ORDER BY here
|
||||||
|
// into a syntax error. Sorting belongs in `sortItems`.
|
||||||
|
'foreign_table_where' => 'AND {#tx_vitec_domain_model_usecase}.{#sys_language_uid} IN (0,-1)',
|
||||||
|
'sortItems' => ['label' => 'asc'],
|
||||||
|
'size' => 8,
|
||||||
|
'minitems' => 0,
|
||||||
|
'maxitems' => 20,
|
||||||
|
'enableMultiSelectFilterTextfield' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// Same vocabulary as the story and market lists (grid|list|carousel|50-50),
|
||||||
|
// so the front end renders these cards with the component it already has.
|
||||||
|
'tx_vitec_success_stories_layout' => [
|
||||||
|
'exclude' => true,
|
||||||
|
'displayCond' => 'FIELD:tx_vitec_success_stories:REQ:true',
|
||||||
|
'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:sys_category.tx_vitec_success_stories_layout',
|
||||||
|
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:sys_category.tx_vitec_success_stories_layout.description',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'select',
|
||||||
|
'renderType' => 'selectSingle',
|
||||||
|
'default' => 'grid',
|
||||||
|
'items' => [
|
||||||
|
['label' => 'Grid', 'value' => 'grid'],
|
||||||
|
['label' => 'List', 'value' => 'list'],
|
||||||
|
['label' => 'Carousel', 'value' => 'carousel'],
|
||||||
|
['label' => '50 / 50', 'value' => '50-50'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add the fields to the TCA
|
// Add the fields to the TCA
|
||||||
@@ -43,4 +84,15 @@ $customSysCategoryColumns = [
|
|||||||
'class, filetype, type',
|
'class, filetype, type',
|
||||||
'',
|
'',
|
||||||
'after:description'
|
'after:description'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Success stories go into the existing "Options" tab. That tab comes from
|
||||||
|
// EXT:news (it holds `images`), so anchoring after that field puts our field
|
||||||
|
// INSIDE it - adding our own --div--;...options would produce a second tab of
|
||||||
|
// the same name. EXT:news loads before EXT:vitec, so the anchor exists.
|
||||||
|
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes(
|
||||||
|
'sys_category',
|
||||||
|
'tx_vitec_success_stories, tx_vitec_success_stories_layout',
|
||||||
|
'',
|
||||||
|
'after:images'
|
||||||
);
|
);
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Document identifier** | EVO‑VITEC‑HL‑001 |
|
| **Document identifier** | EVO‑VITEC‑HL‑001 |
|
||||||
| **Version** | 1.15 |
|
| **Version** | 1.17 |
|
||||||
| **Status** | Released |
|
| **Status** | Released |
|
||||||
| **Date** | 2026‑09‑11 |
|
| **Date** | 2026‑09‑11 |
|
||||||
| **Applies to** | `evomedien/vitec` on TYPO3 v14.3 (headless) |
|
| **Applies to** | `evomedien/vitec` on TYPO3 v14.3 (headless) |
|
||||||
@@ -26,9 +26,10 @@
|
|||||||
| 1.10 | 2026‑08‑18 | **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`, page‑level shape) and `background` (7.4). Two record link handlers (`download`, `product`) added to the link browser, resolved server‑side 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 B‑4 target (10.2). Backend‑only: `relatedprodukt` moved from the Misc tab to General. Editorial: the document footer had been stuck at v1.7 since v1.8. |
|
| 1.10 | 2026‑08‑18 | **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`, page‑level shape) and `background` (7.4). Two record link handlers (`download`, `product`) added to the link browser, resolved server‑side 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 B‑4 target (10.2). Backend‑only: `relatedprodukt` moved from the Misc tab to General. Editorial: the document footer had been stuck at v1.7 since v1.8. |
|
||||||
| 1.11 | 2026‑08‑21 | **New interface: site search.** Apache Solr 10 (dedicated VPS behind an HTTPS reverse proxy) with `apache-solr-for-typo3/solr` 14.0.0-RC1. The EXT:solr results plugin `solr_pi_results` on the search page is rendered headless by `SearchJsonRenderer` (payload key `search`) - request/response contract in the new Clause 7.16. Indexed corpus: pages plus product, market (`detail_page` only), use-case, news and download records; result `type` vocabulary `page\|product\|market\|story\|news\|download`. Downloads gained the canonical route `/download/<slug>` (uid route kept for the record links) and `private_download` is now enforced by `DownloadFileResolver` (5.3, 9.12). The VITEC Set now declares the solr set as a dependency - overriding a foreign set’s TypoScript requires loading after it (5.2). Editorial: the header table had been stuck at v1.9 since v1.10. |
|
| 1.11 | 2026‑08‑21 | **New interface: site search.** Apache Solr 10 (dedicated VPS behind an HTTPS reverse proxy) with `apache-solr-for-typo3/solr` 14.0.0-RC1. The EXT:solr results plugin `solr_pi_results` on the search page is rendered headless by `SearchJsonRenderer` (payload key `search`) - request/response contract in the new Clause 7.16. Indexed corpus: pages plus product, market (`detail_page` only), use-case, news and download records; result `type` vocabulary `page\|product\|market\|story\|news\|download`. Downloads gained the canonical route `/download/<slug>` (uid route kept for the record links) and `private_download` is now enforced by `DownloadFileResolver` (5.3, 9.12). The VITEC Set now declares the solr set as a dependency - overriding a foreign set’s TypoScript requires loading after it (5.2). Editorial: the header table had been stuck at v1.9 since v1.10. |
|
||||||
| 1.12 | 2026‑08‑24 | **Interface change (additive).** The search endpoint (7.16) gained a type filter and facet counts: request parameter `filter` (a value from the `type` vocabulary; the natural name `type` is unavailable - it is TYPO3’s reserved page-type parameter), response keys `filter` (active filter or null) and `facets.type` (per-type document counts with `active` flags; counts stay complete while a filter is active, except when the filtered result is empty). Editorial baseline of 76 managed synonyms imported into `core_en` (codecs, acquired-brand names such as `exterity => avedia`, UK/US spellings, common misspellings) - synonyms apply at query time, no re-index. New autocomplete endpoint: the EXT:solr suggest plugin as lean JSON page type 7384, deliberately USER_INT (7.16). |
|
| 1.12 | 2026‑08‑24 | **Interface change (additive).** The search endpoint (7.16) gained a type filter and facet counts: request parameter `filter` (a value from the `type` vocabulary; the natural name `type` is unavailable - it is TYPO3’s reserved page-type parameter), response keys `filter` (active filter or null) and `facets.type` (per-type document counts with `active` flags; counts stay complete while a filter is active, except when the filtered result is empty). Editorial baseline of 76 managed synonyms imported into `core_en` (codecs, acquired-brand names such as `exterity => avedia`, UK/US spellings, common misspellings) - synonyms apply at query time, no re-index. New autocomplete endpoint: the EXT:solr suggest plugin as lean JSON page type 7384, deliberately USER_INT (7.16). |
|
||||||
| 1.13 | 2026‑09‑11 | **New plugin** `vitec_productfinder` (`ProductFinderJsonRenderer`, payload key `productfinder`): the product category hierarchy on its own, as the data source for the filter selects above the product lists (7.15.3, 8). No change to `vitec_productlist` — filtering happens client side over the products already delivered, keyed on the category uids this payload carries. |
|
|
||||||
| 1.14 | 2026‑09‑11 | **Interface change (additive).** Secondary search facets: stories index their market titles (`market_stringM`, MM relation), products their sys_category titles (`category_stringM`); the search endpoint accepts `market` and `category` GET parameters (values verbatim from `facets.<name>[].value`; an unknown value simply yields zero results) and echoes them as `activeFacets`. Visibility fix: the usecases queue now honours `no_index`/`hideonwebsite`, products defensively `hideonwebsite` - and the excluded story’s document was removed explicitly, because re-indexing never deletes. |
|
| 1.14 | 2026‑09‑11 | **Interface change (additive).** Secondary search facets: stories index their market titles (`market_stringM`, MM relation), products their sys_category titles (`category_stringM`); the search endpoint accepts `market` and `category` GET parameters (values verbatim from `facets.<name>[].value`; an unknown value simply yields zero results) and echoes them as `activeFacets`. Visibility fix: the usecases queue now honours `no_index`/`hideonwebsite`, products defensively `hideonwebsite` - and the excluded story’s document was removed explicitly, because re-indexing never deletes. |
|
||||||
| 1.15 | 2026‑09‑11 | **Corpus change (additive, index side).** Download documents now carry the extracted file text: Solr 10 has no ExtractingRequestHandler, so a dedicated Apache Tika container on the Solr VPS (Caddy route `/tika/*`, own basic-auth credential) extracts PDF/Office contents during indexing (`TikaDownloadContentIndexer` on `BeforeDocumentIsProcessedForIndexingEvent`, fail-soft per 9.5, cached in `var/tika-cache` keyed on path+size+mtime). Datasheet specifications are now searchable ("625i", "genlock"). No interface change. |
|
| 1.15 | 2026‑09‑11 | **Corpus change (additive, index side).** Download documents now carry the extracted file text: Solr 10 has no ExtractingRequestHandler, so a dedicated Apache Tika container on the Solr VPS (Caddy route `/tika/*`, own basic-auth credential) extracts PDF/Office contents during indexing (`TikaDownloadContentIndexer` on `BeforeDocumentIsProcessedForIndexingEvent`, fail-soft per 9.5, cached in `var/tika-cache` keyed on path+size+mtime). Datasheet specifications are now searchable ("625i", "genlock"). No interface change. |
|
||||||
|
| 1.16 | 2026‑09‑11 | **New plugin** `vitec_productfinder` (`ProductFinderJsonRenderer`, payload key `productfinder`): the product category hierarchy on its own, as the data source for the filter selects above the product lists (7.15.3, 8). No change to `vitec_productlist` — filtering happens client side over the products already delivered, keyed on the category uids this payload carries. |
|
||||||
|
| 1.17 | 2026‑09‑11 | **Interface change (additive).** The product list payload gained `successStories` (7.15.2.1): stories an editor attaches to a `sys_category` (tab *Options*, with its own layout), emitted in the list envelope beside `products`. Each card carries `forCategories` — the product categories it came from — so the front end can hide it with the same `?cat=`/`?subcat=` filter it applies to the products. Story links reuse the 7.5 rule, with the detail page taken from the element or the new site setting `vitec.storyDetailPid`. |
|
||||||
|
|
||||||
This document is drafted in the style of, and adopts the terminology conventions of,
|
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
|
ISO/IEC/IEEE 42010 (architecture description), ISO/IEC/IEEE 26514 (information for
|
||||||
@@ -907,6 +908,51 @@ rarely shows. The `layout` vocabulary remains `0`–`3` (see the v1.9 note).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### 7.15.2.1 Success stories below a product list
|
||||||
|
|
||||||
|
The list payload carries a second array beside `products`: the success stories
|
||||||
|
an editor attached to the categories this element renders. The link lives on
|
||||||
|
`sys_category` (tab *Options*), not on the plugin, so one decision serves every
|
||||||
|
list that shows the category.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"successStories": {
|
||||||
|
"layout": "grid",
|
||||||
|
"stories": [
|
||||||
|
{
|
||||||
|
"uid": 12,
|
||||||
|
"title": "Abu Dhabi Islamic Bank",
|
||||||
|
"detailUrl": "/success-stories/abu-dhabi-islamic-bank",
|
||||||
|
"cardImage": { },
|
||||||
|
"categories": [ ],
|
||||||
|
"forCategories": [58]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Envelope per 7.3 (`layout` plus the array); `layout` uses the story-list
|
||||||
|
vocabulary `grid|list|carousel|50-50` and comes from the first category that
|
||||||
|
contributes stories. Cards are the shared story shape of 7.5, so the same
|
||||||
|
front-end component renders them.
|
||||||
|
|
||||||
|
`forCategories` is the addition: the **product** categories the story was
|
||||||
|
attached to — not to be confused with `categories`, which stays the story's own
|
||||||
|
topic categories. The front end filters on it, using the same `?cat=` /
|
||||||
|
`?subcat=` parameters the product finder (7.15.3) writes: a card stays visible
|
||||||
|
while one of its categories is selected. Stories are collected over the
|
||||||
|
configured categories **and their descendants**, deduplicated, each carrying
|
||||||
|
every category it came from. An element configured to show all products has no
|
||||||
|
category context and returns the empty envelope.
|
||||||
|
|
||||||
|
`detailUrl` follows 7.5: the story's own `detail_page` wins; otherwise the URL
|
||||||
|
is built from the detail page's parent path plus the story slug — the form
|
||||||
|
`SuccessStoryPathRewrite` maps back. That page is taken from the element's
|
||||||
|
FlexForm, falling back to the site setting `vitec.storyDetailPid`, and
|
||||||
|
`detailUrl` is `null` when neither is set.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
#### 7.15.3 Product Finder payload (`vitec_productfinder`, key `productfinder`)
|
#### 7.15.3 Product Finder payload (`vitec_productfinder`, key `productfinder`)
|
||||||
|
|
||||||
The filter above the product lists is fed by its own element. It carries the
|
The filter above the product lists is fed by its own element. It carries the
|
||||||
|
|||||||
@@ -27,6 +27,18 @@
|
|||||||
<trans-unit id="tx_vitec_domain_model_product.slug.description" resname="tx_vitec_domain_model_product.slug.description">
|
<trans-unit id="tx_vitec_domain_model_product.slug.description" resname="tx_vitec_domain_model_product.slug.description">
|
||||||
<source>Slug</source>
|
<source>Slug</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="sys_category.tx_vitec_success_stories" resname="sys_category.tx_vitec_success_stories">
|
||||||
|
<source>Success Stories</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sys_category.tx_vitec_success_stories.description" resname="sys_category.tx_vitec_success_stories.description">
|
||||||
|
<source>Shown as cards below the product list of this category. The order set here is the order they appear in.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sys_category.tx_vitec_success_stories_layout" resname="sys_category.tx_vitec_success_stories_layout">
|
||||||
|
<source>Layout</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sys_category.tx_vitec_success_stories_layout.description" resname="sys_category.tx_vitec_success_stories_layout.description">
|
||||||
|
<source>Display variant for these story cards - same options as the success story list.</source>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
@@ -362,3 +362,12 @@ CREATE TABLE tx_vitec_product_workbook (
|
|||||||
tstamp int(11) DEFAULT '0' NOT NULL,
|
tstamp int(11) DEFAULT '0' NOT NULL,
|
||||||
PRIMARY KEY (product_uid)
|
PRIMARY KEY (product_uid)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#
|
||||||
|
# Success stories shown below the product list of a category (2026-09-11).
|
||||||
|
# Comma-separated usecase uids, order = the editor's arrangement.
|
||||||
|
#
|
||||||
|
CREATE TABLE sys_category (
|
||||||
|
tx_vitec_success_stories varchar(1024) DEFAULT '' NOT NULL,
|
||||||
|
tx_vitec_success_stories_layout varchar(30) DEFAULT 'grid' NOT NULL
|
||||||
|
);
|
||||||
+27
-27
@@ -1,27 +1,27 @@
|
|||||||
RewriteEngine On
|
RewriteEngine On
|
||||||
RewriteBase /_frontend/
|
RewriteBase /_frontend/
|
||||||
|
|
||||||
RewriteRule ^index\.html$ - [L]
|
RewriteRule ^index\.html$ - [L]
|
||||||
|
|
||||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
||||||
RewriteCond %{REQUEST_FILENAME} -d
|
RewriteCond %{REQUEST_FILENAME} -d
|
||||||
RewriteRule ^ - [L]
|
RewriteRule ^ - [L]
|
||||||
|
|
||||||
RewriteRule ^ index.html [L]
|
RewriteRule ^ index.html [L]
|
||||||
|
|
||||||
# ── Cache headers ────────────────────────────────────────────────────────────
|
# ── Cache headers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# index.html: never cache — always fetch fresh so the browser picks up
|
# index.html: never cache — always fetch fresh so the browser picks up
|
||||||
# the latest hashed JS/CSS filenames after a deploy.
|
# the latest hashed JS/CSS filenames after a deploy.
|
||||||
<Files "index.html">
|
<Files "index.html">
|
||||||
Header set Cache-Control "no-store, no-cache, must-revalidate"
|
Header set Cache-Control "no-store, no-cache, must-revalidate"
|
||||||
Header set Pragma "no-cache"
|
Header set Pragma "no-cache"
|
||||||
Header set Expires "0"
|
Header set Expires "0"
|
||||||
</Files>
|
</Files>
|
||||||
|
|
||||||
# Hashed assets (JS, CSS, fonts, images inside /assets/):
|
# Hashed assets (JS, CSS, fonts, images inside /assets/):
|
||||||
# Vite appends a content hash to every filename, so these are immutable.
|
# Vite appends a content hash to every filename, so these are immutable.
|
||||||
# Cache them for 1 year — a new deploy produces new filenames automatically.
|
# Cache them for 1 year — a new deploy produces new filenames automatically.
|
||||||
<FilesMatch "\.(js|css|woff2?|ttf|eot|otf)$">
|
<FilesMatch "\.(js|css|woff2?|ttf|eot|otf)$">
|
||||||
Header set Cache-Control "public, max-age=31536000, immutable"
|
Header set Cache-Control "public, max-age=31536000, immutable"
|
||||||
</FilesMatch>
|
</FilesMatch>
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<title>VITEC</title>
|
<title>VITEC</title>
|
||||||
<script type="module" crossorigin src="/_frontend/assets/index-CrOmtYLr.js"></script>
|
<script type="module" crossorigin src="/_frontend/assets/index-BhQWo9uW.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/_frontend/assets/index-CrFFiPFX.css">
|
<link rel="stylesheet" crossorigin href="/_frontend/assets/index-CQJ1iyvA.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="page"></div>␍
|
<div id="page"></div>␍
|
||||||
|
|||||||
Reference in new issue
Block a user