Success-story migration + new plugins + JSON/UX polish
- Success Stories: full migration from old site (48/48, audited), markets n:n, quotation content block, columns content block, pretty SEO detail URLs via SuccessStoryPathRewrite middleware, list/detail split (list page 4 / story page), detailUrl/backUrl - New plugins: VITEC Locations (grid/list/map + RTE map text), VITEC Customer Logos (color/bw logic, only-show-selected), VITEC Card (one plugin for product/story/market/solution with reloading FlexForm + custom backend preview renderer) - Eventlist: layout dropdown (list/grid/teaserbar) in settings - Hero section CB: Images/Video tabs, background video + overlay - Product JSON: full category rootline (parents), fixed missing ConnectionPool import (all-products crash), category tree map - Backend preview CSS: container-query responsive (narrow columns) - Docs: ISO architecture spec, root + extension READMEs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aafcb5dcb6
commit
eae40f4286
110 files changed
+8502
-279
No files matched your search
@@ -45,7 +45,7 @@ final class UsecaseSerializer
|
||||
'teaser' => (string)($u['teaser'] ?? ''),
|
||||
'cardImage' => $this->image($uid, 'card_image'),
|
||||
'customerLogo' => $this->image($uid, 'customer_logo'),
|
||||
'market' => $this->market((int)($u['market'] ?? 0)),
|
||||
'markets' => $this->relationMulti('tx_vitec_usecase_market_mm', 'tx_vitec_domain_model_market', $uid),
|
||||
'categories' => $this->categories($uid),
|
||||
'featured' => (bool)($u['featured'] ?? false),
|
||||
'layoutVariant' => (string)($u['layout_variant'] ?? 'standard'),
|
||||
@@ -75,7 +75,7 @@ final class UsecaseSerializer
|
||||
'contentElements' => $this->contentElements($uid),
|
||||
'related' => [
|
||||
'show' => (bool)($u['show_related'] ?? true),
|
||||
'market' => $base['market'],
|
||||
'markets' => $base['markets'],
|
||||
'solutions' => $this->relationMulti('tx_vitec_usecase_solution_mm', 'tx_vitec_domain_model_solution', $uid),
|
||||
'products' => $this->relationMulti('tx_vitec_usecase_product_mm', 'tx_vitec_domain_model_product', $uid),
|
||||
'categories' => $base['categories'],
|
||||
@@ -129,6 +129,20 @@ final class UsecaseSerializer
|
||||
$imageService = GeneralUtility::makeInstance(ImageService::class);
|
||||
$fileReference = $resourceFactory->getFileReferenceObject((int)$ref['uid']);
|
||||
|
||||
if (str_contains((string)$fileReference->getMimeType(), 'svg')) {
|
||||
// SVG: no processing/srcset — deliver the file as-is.
|
||||
return [
|
||||
'uid' => (int)$ref['uid'],
|
||||
'url' => (string)$fileReference->getPublicUrl(),
|
||||
'title' => (string)($ref['title'] ?? ''),
|
||||
'alternative' => (string)($ref['alternative'] ?? ''),
|
||||
'description' => (string)($ref['description'] ?? ''),
|
||||
'srcset' => [],
|
||||
'properties' => ['mimeType' => $fileReference->getMimeType()],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
$srcset = [];
|
||||
foreach (self::IMAGE_WIDTHS as $width) {
|
||||
$variant = $imageService->applyProcessingInstructions(
|
||||
@@ -165,6 +179,114 @@ final class UsecaseSerializer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All FAL images of one field (multi-reference), same shape as image().
|
||||
*
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
public function images(int $recordUid, string $fieldName, ?string $table = null): array
|
||||
{
|
||||
try {
|
||||
$table = $table ?? self::TABLE;
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_file_reference');
|
||||
$refs = $qb
|
||||
->select('sfr.uid')
|
||||
->from('sys_file_reference', 'sfr')
|
||||
->where(
|
||||
$qb->expr()->eq('sfr.tablenames', $qb->createNamedParameter($table, ParameterType::STRING)),
|
||||
$qb->expr()->eq('sfr.fieldname', $qb->createNamedParameter($fieldName, ParameterType::STRING)),
|
||||
$qb->expr()->eq('sfr.uid_foreign', $qb->createNamedParameter($recordUid, ParameterType::INTEGER)),
|
||||
$qb->expr()->eq('sfr.deleted', 0),
|
||||
$qb->expr()->eq('sfr.hidden', 0)
|
||||
)
|
||||
->orderBy('sfr.sorting_foreign')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
$out = [];
|
||||
foreach ($refs as $i => $r) {
|
||||
// reuse the single-image resolver per reference position
|
||||
$img = $this->imageByReferenceUid((int)$r['uid']);
|
||||
if ($img !== null) {
|
||||
$out[] = $img;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve one sys_file_reference uid to the standard image shape.
|
||||
*
|
||||
* @return array<string,mixed>|null
|
||||
*/
|
||||
private function imageByReferenceUid(int $refUid): ?array
|
||||
{
|
||||
try {
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_file_reference');
|
||||
$ref = $qb
|
||||
->select('uid', 'title', 'description', 'alternative', 'crop')
|
||||
->from('sys_file_reference')
|
||||
->where($qb->expr()->eq('uid', $qb->createNamedParameter($refUid, ParameterType::INTEGER)))
|
||||
->executeQuery()
|
||||
->fetchAssociative();
|
||||
if (!$ref) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
|
||||
$imageService = GeneralUtility::makeInstance(ImageService::class);
|
||||
$fileReference = $resourceFactory->getFileReferenceObject((int)$ref['uid']);
|
||||
|
||||
if (str_contains((string)$fileReference->getMimeType(), 'svg')) {
|
||||
// SVG: no processing/srcset — deliver the file as-is.
|
||||
return [
|
||||
'uid' => (int)$ref['uid'],
|
||||
'url' => (string)$fileReference->getPublicUrl(),
|
||||
'title' => (string)($ref['title'] ?? ''),
|
||||
'alternative' => (string)($ref['alternative'] ?? ''),
|
||||
'description' => (string)($ref['description'] ?? ''),
|
||||
'srcset' => [],
|
||||
'properties' => ['mimeType' => $fileReference->getMimeType()],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
$srcset = [];
|
||||
foreach (self::IMAGE_WIDTHS as $width) {
|
||||
$variant = $imageService->applyProcessingInstructions(
|
||||
$fileReference,
|
||||
['width' => $width, 'crop' => $ref['crop'] ?? null]
|
||||
);
|
||||
$srcset[] = ['url' => $imageService->getImageUri($variant), 'width' => $width, 'descriptor' => $width . 'w'];
|
||||
}
|
||||
$default = $imageService->applyProcessingInstructions(
|
||||
$fileReference,
|
||||
['width' => 800, 'crop' => $ref['crop'] ?? null]
|
||||
);
|
||||
|
||||
return [
|
||||
'uid' => (int)$ref['uid'],
|
||||
'url' => $imageService->getImageUri($default),
|
||||
'title' => (string)($ref['title'] ?? ''),
|
||||
'alternative' => (string)($ref['alternative'] ?? ''),
|
||||
'description' => (string)($ref['description'] ?? ''),
|
||||
'srcset' => $srcset,
|
||||
'properties' => [
|
||||
'width' => $fileReference->getProperty('width'),
|
||||
'height' => $fileReference->getProperty('height'),
|
||||
'mimeType' => $fileReference->getProperty('mime_type'),
|
||||
],
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FAL video (public URL only). $table as in image().
|
||||
*
|
||||
@@ -367,10 +489,39 @@ final class UsecaseSerializer
|
||||
*/
|
||||
private function resolveInlineElement(array $row): ?array
|
||||
{
|
||||
if ((string)($row['CType'] ?? '') === self::COLUMNS_CTYPE) {
|
||||
$ctype = (string)($row['CType'] ?? '');
|
||||
if ($ctype === self::COLUMNS_CTYPE) {
|
||||
return $this->resolveColumnsElement($row);
|
||||
}
|
||||
return ContentElementResolver::normaliseRecord($row);
|
||||
if ($ctype === 'vitec_quotation') {
|
||||
$uid = (int)$row['uid'];
|
||||
$logoField = array_key_exists('vitec_quote_logo', $row) ? 'vitec_quote_logo' : 'quote_logo';
|
||||
return [
|
||||
'id' => $uid,
|
||||
'type' => 'vitec_quotation',
|
||||
'colPos' => (int)($row['colPos'] ?? 0),
|
||||
'sorting' => (int)($row['sorting'] ?? 0),
|
||||
'header' => (string)($row['header'] ?? ''),
|
||||
'quote' => (string)($row['vitec_quote'] ?? $row['quote'] ?? ''),
|
||||
'name' => (string)($row['vitec_quote_name'] ?? $row['quote_name'] ?? ''),
|
||||
'position' => (string)($row['vitec_quote_position'] ?? $row['quote_position'] ?? ''),
|
||||
'logo' => $this->image($uid, $logoField, 'tt_content'),
|
||||
];
|
||||
}
|
||||
$el = ContentElementResolver::normaliseRecord($row);
|
||||
if (is_array($el)) {
|
||||
$uid = (int)$row['uid'];
|
||||
$media = [];
|
||||
foreach (['image', 'assets', 'media'] as $f) {
|
||||
foreach ($this->images($uid, $f, 'tt_content') as $img) {
|
||||
$media[] = $img;
|
||||
}
|
||||
}
|
||||
if ($media !== []) {
|
||||
$el['images'] = $media;
|
||||
}
|
||||
}
|
||||
return $el;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -445,15 +596,12 @@ final class UsecaseSerializer
|
||||
$uid = (int)$it['uid'];
|
||||
$get = static fn (string $k): string => (string)($it['vitec_' . $k] ?? $it[$k] ?? '');
|
||||
|
||||
$imageField = array_key_exists('vitec_image', $it) ? 'vitec_image' : 'image';
|
||||
$videoField = array_key_exists('vitec_video', $it) ? 'vitec_video' : 'video';
|
||||
|
||||
return [
|
||||
'type' => $get('item_type') !== '' ? $get('item_type') : 'text',
|
||||
'headline' => $get('headline'),
|
||||
'text' => $get('text'),
|
||||
'image' => $this->image($uid, $imageField, $table),
|
||||
'video' => $this->video($uid, $videoField, $table),
|
||||
'image' => $this->image($uid, 'vitec_image', $table) ?? $this->image($uid, 'image', $table),
|
||||
'video' => $this->video($uid, 'vitec_video', $table) ?? $this->video($uid, 'video', $table),
|
||||
'link' => $get('link'),
|
||||
'linkLabel' => $get('link_label'),
|
||||
];
|
||||
|
||||
Reference in new issue
Block a user