Initial Commit

This commit is contained in:
khaccount committed 2026-05-18 14:46:25 +02:00
1 parent 4f495177e4
commit b6d2142214
166 files changed
+13952 -178

No files matched your search

@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\DataProcessing;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* Collect container children grouped by colPos and emit a lean, transport-
* ready structure. Each child is normalised to:
* { id, type, colPos, sorting, appearance, data }
* `data` only contains non-empty content-relevant fields.
*
* Exception-safe.
*/
final class ContainerChildrenProcessor implements DataProcessorInterface
{
/** Fields that go to the envelope (not to `data`). */
private const ENVELOPE = [
'uid', 'CType', 'colPos', 'sorting',
'layout', 'frame_class', 'space_before_class', 'space_after_class',
];
/** Technical / system / TCA-default fields — never sent to frontend. */
private const SYSTEM_FIELDS = [
// versioning / language / workspace / housekeeping
'pid', 'sys_language_uid', 'l18n_parent', 'l18n_diffsource',
'l10n_source', 'l10n_state', 'l10n_parent',
't3_origuid', 'tx_impexp_origuid',
'tx_container_parent',
'tstamp', 'crdate', 'cruser_id',
'hidden', 'deleted', 'starttime', 'endtime', 'fe_group',
't3ver_oid', 't3ver_wsid', 't3ver_state', 't3ver_stage',
't3ver_id', 't3ver_label', 't3ver_count', 't3ver_tstamp',
'editlock', 'sorting_foreign', 'rowDescription',
'spaceBefore', 'spaceAfter', // legacy
// TCA defaults that TYPO3 always sets on every tt_content row,
// regardless of CType — rarely relevant to the frontend:
'imagecols', 'sectionIndex', 'linkToTop', 'recursive', 'date',
'bullets_type', 'cols',
'table_delimiter', 'table_enclosure', 'table_header_position',
'table_tfoot', 'table_caption',
'filelink_size', 'filelink_sorting', 'filelink_sorting_direction',
'uploads_description', 'uploads_type',
];
/** Fields whose 0/empty value is still meaningful. */
private const KEEP_IF_ZERO = [
'header_layout',
];
public function process(
ContentObjectRenderer $cObj,
array $contentObjectConfiguration,
array $processorConfiguration,
array $processedData
): array {
$as = (string)($processorConfiguration['as'] ?? 'items');
try {
$parentUid = (int)($cObj->data['uid'] ?? 0);
if ($parentUid <= 0) {
$processedData[$as] = [];
return $processedData;
}
$pid = (int)($cObj->data['pid'] ?? 0);
$sysLanguageUid = (int)($cObj->data['sys_language_uid'] ?? 0);
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$rows = $qb
->select('*')
->from('tt_content')
->where(
$qb->expr()->eq('tx_container_parent', $qb->createNamedParameter($parentUid, Connection::PARAM_INT)),
$qb->expr()->eq('pid', $qb->createNamedParameter($pid, Connection::PARAM_INT)),
$qb->expr()->eq('sys_language_uid', $qb->createNamedParameter($sysLanguageUid, Connection::PARAM_INT))
)
->orderBy('colPos')
->addOrderBy('sorting')
->executeQuery()
->fetchAllAssociative();
$byColPos = [];
foreach ($rows as $record) {
$byColPos[(int)$record['colPos']][] = $this->normalise($record);
}
ksort($byColPos);
$items = [];
foreach ($byColPos as $colPos => $contentElements) {
$items[] = [
'config' => ['colPos' => $colPos],
'contentElements' => $contentElements,
];
}
$processedData[$as] = $items;
} catch (\Throwable $e) {
$processedData[$as] = [];
}
return $processedData;
}
private function normalise(array $record): array
{
$data = [];
foreach ($record as $field => $value) {
if (in_array($field, self::ENVELOPE, true)) {
continue;
}
if (in_array($field, self::SYSTEM_FIELDS, true)) {
continue;
}
if ($this->isEmpty($value) && !in_array($field, self::KEEP_IF_ZERO, true)) {
continue;
}
$data[$field] = $this->castValue($field, $value);
}
return [
'id' => (int)$record['uid'],
'type' => (string)$record['CType'],
'colPos' => (int)$record['colPos'],
'sorting' => (int)($record['sorting'] ?? 0),
'appearance' => [
'layout' => (string)($record['layout'] ?? ''),
'frameClass' => (string)($record['frame_class'] ?? 'default'),
'spaceBefore' => (string)($record['space_before_class'] ?? ''),
'spaceAfter' => (string)($record['space_after_class'] ?? ''),
],
'data' => (object)$data,
];
}
private function isEmpty(mixed $value): bool
{
return $value === null
|| $value === ''
|| $value === 0
|| $value === '0';
}
private function castValue(string $field, mixed $value): mixed
{
if (in_array($field, ['header_layout'], true)) {
return (int)$value;
}
return $value;
}
}
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\DataProcessing;
use Evomedien\Vitec\Domain\Model\Dto\ProductListContentElement;
use Evomedien\Vitec\Domain\Repository\ProductRepository;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* Data processor for vitec_productlist content element
*/
class ProductListProcessor implements DataProcessorInterface
{
public function __construct(
protected readonly ProductRepository $productRepository,
protected readonly FlexFormService $flexFormService
) {
}
public function process(
ContentObjectRenderer $cObj,
array $contentObjectConfiguration,
array $processorConfiguration,
array $processedData
): array {
// Only process if this is a vitec_productlist
if (($processedData['data']['list_type'] ?? '') !== 'vitec_productlist') {
return $processedData;
}
// Get FlexForm settings
$flexFormData = $this->flexFormService->convertFlexFormContentToArray(
$processedData['data']['pi_flexform'] ?? ''
);
$settings = $flexFormData['settings'] ?? [];
// Extract and sanitize category UIDs from FlexForm
$categoryUids = array_filter(
array_map('intval', explode(',', (string)($settings['categories'] ?? '')))
);
// Get filtered products based on categories
$products = $this->productRepository->findFilteredProducts($categoryUids);
// Serialize products directly
$productsData = [];
foreach ($products as $product) {
$categories = [];
if ($product->getCategories()) {
foreach ($product->getCategories() as $category) {
$categories[] = [
'uid' => $category->getUid(),
'title' => $category->getTitle(),
];
}
}
$image = null;
if ($product->getProductimage() && $product->getProductimage()->count() > 0) {
$imageObject = $product->getProductimage()->current();
$image = [
'uid' => $imageObject->getUid(),
'url' => $imageObject->getOriginalResource()->getPublicUrl(),
'title' => $imageObject->getTitle(),
'alternative' => $imageObject->getAlternative(),
'description' => $imageObject->getDescription(),
];
}
$productsData[] = [
'uid' => $product->getUid(),
'title' => $product->getTitle(),
'subtitle' => $product->getSubtitle(),
'teaser' => $product->getTeaser(),
'description' => $product->getDescription(),
'slug' => $product->getSlug(),
'categories' => $categories,
'image' => $image,
];
}
$processedData['products'] = $productsData;
return $processedData;
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\DataProcessing;
use Evomedien\Vitec\Domain\Repository\ProductRepository;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/**
* Simple and direct data processor for vitec_productlist
*/
class VitecProductProcessor implements DataProcessorInterface
{
public function __construct(
protected readonly ProductRepository $productRepository,
protected readonly FlexFormService $flexFormService
) {
}
public function process(
ContentObjectRenderer $cObj,
array $contentObjectConfiguration,
array $processorConfiguration,
array $processedData
): array {
// Only process vitec_productlist
if (($processedData['data']['list_type'] ?? '') !== 'vitec_productlist') {
return $processedData;
}
// Get FlexForm settings
$flexFormData = $this->flexFormService->convertFlexFormContentToArray(
$processedData['data']['pi_flexform'] ?? ''
);
$settings = $flexFormData['settings'] ?? [];
// Extract category UIDs
$categoryUids = array_filter(
array_map('intval', explode(',', (string)($settings['categories'] ?? '')))
);
// Get products
$products = $this->productRepository->findFilteredProducts($categoryUids);
// Serialize products
$productsData = [];
foreach ($products as $product) {
$productsData[] = [
'uid' => $product->getUid(),
'title' => $product->getTitle(),
'slug' => $product->getSlug(),
];
}
// Add to processedData at content level
if (!isset($processedData['content'])) {
$processedData['content'] = [];
}
$processedData['content']['products'] = $productsData;
return $processedData;
}
}