Add Container Options
This commit is contained in:
@@ -76,6 +76,11 @@ final class ContainerChildrenProcessor implements DataProcessorInterface
|
||||
*/
|
||||
private const CONTAINER_FIELDS = [
|
||||
'tx_vitec_gap',
|
||||
// Background settings belong to the container, not to what sits inside
|
||||
// it. They carry non-empty defaults ('none', 'cover', 'center center'),
|
||||
// so without this they would show up in every single child's `data`.
|
||||
'tx_vitec_bg_variant', 'tx_vitec_bg_image', 'tx_vitec_bg_size',
|
||||
'tx_vitec_bg_size_percent', 'tx_vitec_bg_position',
|
||||
'tx_vitec_col1_align', 'tx_vitec_col1_justify',
|
||||
'tx_vitec_col2_align', 'tx_vitec_col2_justify',
|
||||
'tx_vitec_col3_align', 'tx_vitec_col3_justify',
|
||||
|
||||
104
packages/vitec/Classes/UserFunc/ContainerBackgroundRenderer.php
Normal file
104
packages/vitec/Classes/UserFunc/ContainerBackgroundRenderer.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\UserFunc;
|
||||
|
||||
use Evomedien\Vitec\Service\UsecaseSerializer;
|
||||
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc emitting the background block of a VITEC container.
|
||||
*
|
||||
* The container elements are rendered entirely in TypoScript
|
||||
* (Configuration/TypoScript/Headless/vitec_containers.typoscript), where plain
|
||||
* fields are TEXT objects. A FAL image is not a plain field, so it comes through
|
||||
* here — and going through `UsecaseSerializer::image()` rather than the headless
|
||||
* FilesProcessor is deliberate: Clause 9.2 puts image serialisation in one place,
|
||||
* so a background image has the same `{url, srcset, properties}` shape as every
|
||||
* other image in this JSON. One shape for the front end instead of two.
|
||||
*
|
||||
* Emits nothing at all when no image is set — the whole `background` key then
|
||||
* stays out of the payload, which is a clearer signal than an object with a null
|
||||
* image and two orphaned CSS settings.
|
||||
*
|
||||
* `size` and `position` are stored as CSS-ready values (`cover`, `left top`, …).
|
||||
* The single exception is `stretch`, which has no CSS keyword and the front end
|
||||
* maps to `100% 100%`; see the TCA comment in tt_content_vitec_shared.php.
|
||||
*
|
||||
* Fail-soft per Clause 9.5: any error yields an empty string, never an exception
|
||||
* that would take the surrounding content element down with it.
|
||||
*/
|
||||
class ContainerBackgroundRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 no longer assigns `$classObj->cObj` dynamically. The renderer is
|
||||
* handed over through this setter, and only when the method exists at all -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* `is_callable([$classObj, 'setContentObjectRenderer'])`. Without the method
|
||||
* `$this->cObj` stays null and the userFunc never sees its own record.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
try {
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$uid = (int)($row['uid'] ?? 0);
|
||||
if ($uid <= 0) {
|
||||
return '';
|
||||
}
|
||||
// No shortcut on the tx_vitec_bg_image counter: sys_file_reference is
|
||||
// the single source of truth, and a counter that is stale or missing
|
||||
// (schema not updated yet) would silently swallow a set image. One
|
||||
// indexed query per container is cheaper than that class of bug.
|
||||
|
||||
$image = GeneralUtility::makeInstance(UsecaseSerializer::class)
|
||||
->image($uid, 'tx_vitec_bg_image', 'tt_content');
|
||||
if ($image === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string)json_encode([
|
||||
'image' => $image,
|
||||
'size' => $this->resolveSize($row),
|
||||
'position' => (string)($row['tx_vitec_bg_position'] ?? 'center center'),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The stored size, resolved into something the front end can hand straight
|
||||
* to CSS. `custom` is the only value that needs a second field; turning it
|
||||
* into "80%" here keeps the promise that `size` is always usable as-is,
|
||||
* instead of making every consumer look up a companion field.
|
||||
*
|
||||
* A custom size without a usable percentage falls back to `auto` - the CSS
|
||||
* default - rather than emitting "0%" or the meaningless word "custom".
|
||||
*
|
||||
* @param array<string,mixed> $row
|
||||
*/
|
||||
private function resolveSize(array $row): string
|
||||
{
|
||||
$size = (string)($row['tx_vitec_bg_size'] ?? 'cover');
|
||||
if ($size !== 'custom') {
|
||||
return $size;
|
||||
}
|
||||
$percent = (int)($row['tx_vitec_bg_size_percent'] ?? 0);
|
||||
return $percent > 0 ? $percent . '%' : 'auto';
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc: render the VITEC customer logos as JSON (headless).
|
||||
@@ -29,13 +30,28 @@ use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
*/
|
||||
class CustomerlogosJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
private const TABLE = 'tx_vitec_domain_model_customer';
|
||||
private const IMAGE_WIDTHS = [200, 400];
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === 'vitec_customerlogos') {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use Evomedien\Vitec\Service\RteResolver;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc to render the event list (tx_vitec_domain_model_event) as JSON.
|
||||
@@ -30,6 +31,21 @@ use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
*/
|
||||
class EventlistJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent of the region categories. Hard-coded like the other taxonomy roots
|
||||
* in this extension (Annex B-5 tracks them); making it a FlexForm setting
|
||||
@@ -42,7 +58,7 @@ class EventlistJsonRenderer
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
// 1) cObj data path
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === 'vitec_eventlist') {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc: render ONE of the VITEC form plugins as JSON (headless).
|
||||
@@ -25,10 +26,25 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
*/
|
||||
class FormsJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && isset(FormDefinitions::CTYPE_MAP[(string)($row['CType'] ?? '')])) {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -27,12 +27,27 @@ use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
*/
|
||||
class LocationsJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
private const TABLE = 'tx_vitec_domain_model_location';
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === 'vitec_locationlist') {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc: render all VITEC markets as JSON (headless).
|
||||
@@ -38,13 +39,28 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
*/
|
||||
class MarketListJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
private const TABLE = 'tx_vitec_domain_model_market';
|
||||
private const CTYPE = 'vitec_marketlist';
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === self::CTYPE) {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use Evomedien\Vitec\Service\LinkResolver;
|
||||
use Evomedien\Vitec\Service\RteResolver;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc to render a single market as JSON for headless output.
|
||||
@@ -24,11 +25,26 @@ use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
*/
|
||||
class MarketShowJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
// 1) cObj data path
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === 'vitec_marketshow') {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc: render the VITEC Card plugin as JSON (headless).
|
||||
@@ -29,6 +30,21 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
*/
|
||||
class ModelcardJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
private const MODEL_TABLES = [
|
||||
'product' => 'tx_vitec_domain_model_product',
|
||||
'story' => 'tx_vitec_domain_model_usecase',
|
||||
@@ -39,7 +55,7 @@ class ModelcardJsonRenderer
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === 'vitec_modelcard') {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use Evomedien\Vitec\Service\RteResolver;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* Headless JSON renderer for EXT:news content elements.
|
||||
@@ -25,6 +26,21 @@ use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
*/
|
||||
final class NewsJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
private const NEWS_CTYPES = [
|
||||
'news_pi1',
|
||||
'news_newsliststicky',
|
||||
@@ -60,7 +76,7 @@ final class NewsJsonRenderer
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
try {
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row !== null && in_array((string)($row['CType'] ?? ''), self::NEWS_CTYPES, true)) {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use Evomedien\Vitec\Service\RteResolver;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc to render product list as JSON for headless.
|
||||
@@ -30,11 +31,26 @@ use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
*/
|
||||
class ProductListJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
// 1) cObj data path
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === 'vitec_productlist') {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
|
||||
use TYPO3\CMS\Core\Resource\FileReference;
|
||||
use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc to render a single product as JSON for headless output.
|
||||
@@ -28,11 +29,26 @@ use Doctrine\DBAL\ParameterType;
|
||||
*/
|
||||
class ProductShowJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
// 1) cObj data path
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === 'vitec_productshow') {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use Evomedien\Vitec\Service\LinkResolver;
|
||||
use Evomedien\Vitec\Service\RteResolver;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc to render a single solution as JSON for headless output.
|
||||
@@ -24,11 +25,26 @@ use TYPO3\CMS\Extbase\Service\ImageService;
|
||||
*/
|
||||
class SolutionShowJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
// 1) cObj data path
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === 'vitec_solutionshow') {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc: render the Success Story list as JSON (headless).
|
||||
@@ -22,12 +23,27 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
*/
|
||||
class UsecaseListJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
private const TABLE = 'tx_vitec_domain_model_usecase';
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === 'vitec_usecaselist') {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* UserFunc: render a single Success Story as JSON (headless).
|
||||
@@ -21,12 +22,27 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
*/
|
||||
class UsecaseShowJsonRenderer
|
||||
{
|
||||
private ?ContentObjectRenderer $cObj = null;
|
||||
|
||||
/**
|
||||
* TYPO3 v14 hands the ContentObjectRenderer over through this setter only -
|
||||
* ContentObjectRenderer::callUserFunction() duck-types it with
|
||||
* is_callable([$classObj, 'setContentObjectRenderer']). Without the method
|
||||
* $this->cObj stays null, the cObj branch of render() never fires and the
|
||||
* call falls through to page discovery, which picks the FIRST element of
|
||||
* this CType on the page rather than the one actually being rendered.
|
||||
*/
|
||||
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
|
||||
{
|
||||
$this->cObj = $cObj;
|
||||
}
|
||||
|
||||
private const TABLE = 'tx_vitec_domain_model_usecase';
|
||||
|
||||
#[AsAllowedCallable]
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
|
||||
$row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null;
|
||||
if ($row && (string)($row['CType'] ?? '') === 'vitec_usecaseshow') {
|
||||
return $this->renderForRecord($row);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
'--palette--;;general,
|
||||
--palette--;;headers,
|
||||
tx_vitec_bg_variant,
|
||||
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:bg_image.palette;vitec_background,
|
||||
pi_flexform;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:container.flexform.label,
|
||||
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
|
||||
--palette--;;frames,
|
||||
|
||||
@@ -27,6 +27,94 @@ use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
],
|
||||
]);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Shared background image for the VITEC containers, with the two settings a
|
||||
// background actually needs: how it scales and where it sits.
|
||||
//
|
||||
// Deliberately no crop variants (Clause 9.9): a background is framed by
|
||||
// background-size / background-position in CSS, not by cropping the file.
|
||||
// Cropping here would fight the two selects below.
|
||||
//
|
||||
// The values are written so the front end can hand them straight to CSS.
|
||||
// `stretch` is the one exception - it has no CSS keyword and maps to
|
||||
// `100% 100%`; spelled out here because "stretch" is what an editor looks
|
||||
// for, not "100% 100%".
|
||||
// -------------------------------------------------------------------------
|
||||
ExtensionManagementUtility::addTCAcolumns('tt_content', [
|
||||
'tx_vitec_bg_image' => [
|
||||
'label' => $l . 'bg_image.label',
|
||||
'description' => $l . 'bg_image.description',
|
||||
'config' => [
|
||||
'type' => 'file',
|
||||
'maxitems' => 1,
|
||||
'allowed' => 'common-image-types',
|
||||
],
|
||||
],
|
||||
'tx_vitec_bg_size' => [
|
||||
'label' => $l . 'bg_size.label',
|
||||
// Re-renders the form when the value changes, so the "Size %" field
|
||||
// below appears the moment "Custom" is picked instead of only after
|
||||
// a save. FormEngine keeps the current form state across the reload
|
||||
// (SingleFieldContainer -> ReloadOnFieldChange); whether it asks
|
||||
// first is the editor's own "verify on change" preference.
|
||||
'onChange' => 'reload',
|
||||
'config' => [
|
||||
'type' => 'select',
|
||||
'renderType' => 'selectSingle',
|
||||
'items' => [
|
||||
['label' => $l . 'bg_size.option.cover', 'value' => 'cover'],
|
||||
['label' => $l . 'bg_size.option.contain', 'value' => 'contain'],
|
||||
['label' => $l . 'bg_size.option.stretch', 'value' => 'stretch'],
|
||||
['label' => $l . 'bg_size.option.auto', 'value' => 'auto'],
|
||||
['label' => $l . 'bg_size.option.custom', 'value' => 'custom'],
|
||||
],
|
||||
'default' => 'cover',
|
||||
],
|
||||
],
|
||||
// Only meaningful together with bg_size = custom, and hidden otherwise
|
||||
// so the form never shows a percentage that has no effect. The renderer
|
||||
// resolves it into the emitted `size` (e.g. "80%"), so the front end
|
||||
// keeps getting one CSS-ready value instead of a second special case.
|
||||
'tx_vitec_bg_size_percent' => [
|
||||
'label' => $l . 'bg_size_percent.label',
|
||||
'description' => $l . 'bg_size_percent.description',
|
||||
'displayCond' => 'FIELD:tx_vitec_bg_size:=:custom',
|
||||
'config' => [
|
||||
'type' => 'number',
|
||||
'size' => 6,
|
||||
'default' => 100,
|
||||
'range' => [
|
||||
'lower' => 1,
|
||||
'upper' => 500,
|
||||
],
|
||||
],
|
||||
],
|
||||
'tx_vitec_bg_position' => [
|
||||
'label' => $l . 'bg_position.label',
|
||||
'config' => [
|
||||
'type' => 'select',
|
||||
'renderType' => 'selectSingle',
|
||||
'items' => [
|
||||
['label' => $l . 'bg_position.option.top_left', 'value' => 'left top'],
|
||||
['label' => $l . 'bg_position.option.top_center', 'value' => 'center top'],
|
||||
['label' => $l . 'bg_position.option.top_right', 'value' => 'right top'],
|
||||
['label' => $l . 'bg_position.option.center_left', 'value' => 'left center'],
|
||||
['label' => $l . 'bg_position.option.centered', 'value' => 'center center'],
|
||||
['label' => $l . 'bg_position.option.center_right', 'value' => 'right center'],
|
||||
['label' => $l . 'bg_position.option.bottom_left', 'value' => 'left bottom'],
|
||||
['label' => $l . 'bg_position.option.bottom_center', 'value' => 'center bottom'],
|
||||
['label' => $l . 'bg_position.option.bottom_right', 'value' => 'right bottom'],
|
||||
],
|
||||
'default' => 'center center',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// One palette, so size and position sit on a single row under the image.
|
||||
$GLOBALS['TCA']['tt_content']['palettes']['vitec_background'] = [
|
||||
'showitem' => 'tx_vitec_bg_image, --linebreak--, tx_vitec_bg_size, tx_vitec_bg_size_percent, tx_vitec_bg_position',
|
||||
];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Shared parent-level setting: column gap scale (1–5) for the whole grid.
|
||||
// Concerns the container itself (not a single column); maps to a spacing
|
||||
|
||||
@@ -125,6 +125,12 @@ tt_content.vitec_container.fields.cssClass.data = flexform:pi_flexform:settings.
|
||||
tt_content.vitec_container.fields.cssClass.stdWrap.ifEmpty.cObject = TEXT
|
||||
tt_content.vitec_container.fields.cssClass.stdWrap.ifEmpty.cObject.value =
|
||||
|
||||
# Optional background image with its two CSS settings. The renderer returns an
|
||||
# empty string when no image is set, so the `background` key stays out of the
|
||||
# payload entirely rather than appearing with a null image.
|
||||
tt_content.vitec_container.fields.background = USER
|
||||
tt_content.vitec_container.fields.background.userFunc = Evomedien\Vitec\UserFunc\ContainerBackgroundRenderer->render
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# VITEC · Cards Carousel — container whose children (typically vitec_card
|
||||
# elements) become the slides. Inherits the grid-container JSON structure
|
||||
|
||||
@@ -178,6 +178,72 @@
|
||||
<source>5</source>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="bg_image.palette" resname="bg_image.palette">
|
||||
<source>Background Image</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_image.label" resname="bg_image.label">
|
||||
<source>Background Image</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_image.description" resname="bg_image.description">
|
||||
<source>Optional image behind the whole container. Leave empty for no background image.</source>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="bg_size.label" resname="bg_size.label">
|
||||
<source>Background Size</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_size.option.cover" resname="bg_size.option.cover">
|
||||
<source>Cover (fill, may crop)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_size.option.contain" resname="bg_size.option.contain">
|
||||
<source>Contain (fit, may letterbox)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_size.option.stretch" resname="bg_size.option.stretch">
|
||||
<source>Stretch (distorts the image)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_size.option.auto" resname="bg_size.option.auto">
|
||||
<source>Auto (original size)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_size.option.custom" resname="bg_size.option.custom">
|
||||
<source>Custom (use Size % below)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_size_percent.label" resname="bg_size_percent.label">
|
||||
<source>Size %</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_size_percent.description" resname="bg_size_percent.description">
|
||||
<source>Only used when Background Size is set to Custom. 100 = original width, 50 = half, 200 = double.</source>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="bg_position.label" resname="bg_position.label">
|
||||
<source>Background Position</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_position.option.top_left" resname="bg_position.option.top_left">
|
||||
<source>Top Left</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_position.option.top_center" resname="bg_position.option.top_center">
|
||||
<source>Top Center</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_position.option.top_right" resname="bg_position.option.top_right">
|
||||
<source>Top Right</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_position.option.center_left" resname="bg_position.option.center_left">
|
||||
<source>Center Left</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_position.option.centered" resname="bg_position.option.centered">
|
||||
<source>Centered</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_position.option.center_right" resname="bg_position.option.center_right">
|
||||
<source>Center Right</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_position.option.bottom_left" resname="bg_position.option.bottom_left">
|
||||
<source>Bottom Left</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_position.option.bottom_center" resname="bg_position.option.bottom_center">
|
||||
<source>Bottom Center</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="bg_position.option.bottom_right" resname="bg_position.option.bottom_right">
|
||||
<source>Bottom Right</source>
|
||||
</trans-unit>
|
||||
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -207,6 +207,10 @@ CREATE TABLE tt_content (
|
||||
tx_vitec_col3_justify VARCHAR(20) DEFAULT 'flex-start' NOT NULL,
|
||||
tx_vitec_col4_align VARCHAR(20) DEFAULT 'stretch' NOT NULL,
|
||||
tx_vitec_col4_justify VARCHAR(20) DEFAULT 'flex-start' NOT NULL,
|
||||
tx_vitec_bg_image int(11) unsigned DEFAULT '0' NOT NULL,
|
||||
tx_vitec_bg_size VARCHAR(20) DEFAULT 'cover' NOT NULL,
|
||||
tx_vitec_bg_size_percent int(11) unsigned DEFAULT '100' NOT NULL,
|
||||
tx_vitec_bg_position VARCHAR(20) DEFAULT 'center center' NOT NULL,
|
||||
tx_vitec_usecase_content int(11) unsigned DEFAULT '0' NOT NULL
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user