diff --git a/migrations/check_bg.php b/migrations/check_bg.php new file mode 100644 index 0000000..2faede9 --- /dev/null +++ b/migrations/check_bg.php @@ -0,0 +1,81 @@ + PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC] +); + +echo "=== 1) Do the three columns exist? ===\n"; +$cols = $pdo->query("SHOW COLUMNS FROM tt_content LIKE 'tx_vitec_bg_%'")->fetchAll(); +if ($cols === []) { + echo " NONE. database:updateschema has not created them.\n"; +} else { + foreach ($cols as $c) { + echo sprintf(" %-24s %-18s default=%s\n", $c['Field'], $c['Type'], var_export($c['Default'], true)); + } +} + +echo "\n=== 2) The record itself (uid $uid) ===\n"; +$row = $pdo->query("SELECT uid, pid, CType, deleted, hidden, sys_language_uid, l18n_parent, tx_vitec_bg_variant, tx_vitec_bg_image, tx_vitec_bg_size, tx_vitec_bg_position FROM tt_content WHERE uid = $uid")->fetch(); +if (!$row) { + echo " no tt_content row with uid $uid\n"; +} else { + foreach ($row as $k => $v) { + echo sprintf(" %-24s %s\n", $k, var_export($v, true)); + } +} + +echo "\n=== 3) File references pointing at this record ===\n"; +$refs = $pdo->query( + "SELECT r.uid, r.tablenames, r.fieldname, r.uid_local, r.uid_foreign, r.deleted, r.hidden, r.sorting_foreign, + f.identifier, f.name, f.mime_type + FROM sys_file_reference r LEFT JOIN sys_file f ON f.uid = r.uid_local + WHERE r.uid_foreign = $uid AND r.tablenames = 'tt_content'" +)->fetchAll(); +if ($refs === []) { + echo " NONE - nothing references this record. The image was not saved.\n"; +} else { + foreach ($refs as $r) { + echo sprintf(" ref %-6s field=%-22s file=%s deleted=%s hidden=%s\n", + $r['uid'], $r['fieldname'], (string)$r['identifier'], $r['deleted'], $r['hidden']); + } +} + +echo "\n=== 4) Which fieldnames does tt_content use at all? (top 15) ===\n"; +foreach ($pdo->query("SELECT fieldname, COUNT(*) c FROM sys_file_reference WHERE tablenames='tt_content' AND deleted=0 GROUP BY fieldname ORDER BY c DESC LIMIT 15")->fetchAll() as $r) { + echo sprintf(" %-28s %d\n", $r['fieldname'], $r['c']); +} diff --git a/packages/vitec/Classes/DataProcessing/ContainerChildrenProcessor.php b/packages/vitec/Classes/DataProcessing/ContainerChildrenProcessor.php index c3c0ca6..8faba98 100755 --- a/packages/vitec/Classes/DataProcessing/ContainerChildrenProcessor.php +++ b/packages/vitec/Classes/DataProcessing/ContainerChildrenProcessor.php @@ -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', diff --git a/packages/vitec/Classes/UserFunc/ContainerBackgroundRenderer.php b/packages/vitec/Classes/UserFunc/ContainerBackgroundRenderer.php new file mode 100644 index 0000000..6fcac0b --- /dev/null +++ b/packages/vitec/Classes/UserFunc/ContainerBackgroundRenderer.php @@ -0,0 +1,104 @@ +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 $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'; + } +} diff --git a/packages/vitec/Classes/UserFunc/CustomerlogosJsonRenderer.php b/packages/vitec/Classes/UserFunc/CustomerlogosJsonRenderer.php index 94eb3c2..9a0f829 100755 --- a/packages/vitec/Classes/UserFunc/CustomerlogosJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/CustomerlogosJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/EventlistJsonRenderer.php b/packages/vitec/Classes/UserFunc/EventlistJsonRenderer.php index 80ced8f..4cdd77f 100755 --- a/packages/vitec/Classes/UserFunc/EventlistJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/EventlistJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/FormsJsonRenderer.php b/packages/vitec/Classes/UserFunc/FormsJsonRenderer.php index 9907def..8730cc1 100755 --- a/packages/vitec/Classes/UserFunc/FormsJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/FormsJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/LocationsJsonRenderer.php b/packages/vitec/Classes/UserFunc/LocationsJsonRenderer.php index 065c7a4..0e8bad3 100755 --- a/packages/vitec/Classes/UserFunc/LocationsJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/LocationsJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/MarketListJsonRenderer.php b/packages/vitec/Classes/UserFunc/MarketListJsonRenderer.php index 8af115b..2872087 100644 --- a/packages/vitec/Classes/UserFunc/MarketListJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/MarketListJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/MarketShowJsonRenderer.php b/packages/vitec/Classes/UserFunc/MarketShowJsonRenderer.php index 88ef70d..0020769 100755 --- a/packages/vitec/Classes/UserFunc/MarketShowJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/MarketShowJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/ModelcardJsonRenderer.php b/packages/vitec/Classes/UserFunc/ModelcardJsonRenderer.php index 365c1ad..c1239f8 100755 --- a/packages/vitec/Classes/UserFunc/ModelcardJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/ModelcardJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/NewsJsonRenderer.php b/packages/vitec/Classes/UserFunc/NewsJsonRenderer.php index 568c1d7..f308e64 100644 --- a/packages/vitec/Classes/UserFunc/NewsJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/NewsJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php b/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php index 57de408..d4977a5 100755 --- a/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/ProductShowJsonRenderer.php b/packages/vitec/Classes/UserFunc/ProductShowJsonRenderer.php index ab264ca..77f4330 100755 --- a/packages/vitec/Classes/UserFunc/ProductShowJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/ProductShowJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/SolutionShowJsonRenderer.php b/packages/vitec/Classes/UserFunc/SolutionShowJsonRenderer.php index de83c5e..bb622dd 100755 --- a/packages/vitec/Classes/UserFunc/SolutionShowJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/SolutionShowJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/UsecaseListJsonRenderer.php b/packages/vitec/Classes/UserFunc/UsecaseListJsonRenderer.php index 47b9df2..7bf1705 100755 --- a/packages/vitec/Classes/UserFunc/UsecaseListJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/UsecaseListJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Classes/UserFunc/UsecaseShowJsonRenderer.php b/packages/vitec/Classes/UserFunc/UsecaseShowJsonRenderer.php index f9bc676..40d322b 100755 --- a/packages/vitec/Classes/UserFunc/UsecaseShowJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/UsecaseShowJsonRenderer.php @@ -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); } diff --git a/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_container.php b/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_container.php index 3a8088f..db81d74 100755 --- a/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_container.php +++ b/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_container.php @@ -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, diff --git a/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_shared.php b/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_shared.php index 654b6a6..c98dcb2 100755 --- a/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_shared.php +++ b/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_shared.php @@ -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 diff --git a/packages/vitec/Configuration/TypoScript/Headless/vitec_containers.typoscript b/packages/vitec/Configuration/TypoScript/Headless/vitec_containers.typoscript index 395e411..e70094a 100755 --- a/packages/vitec/Configuration/TypoScript/Headless/vitec_containers.typoscript +++ b/packages/vitec/Configuration/TypoScript/Headless/vitec_containers.typoscript @@ -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 diff --git a/packages/vitec/Resources/Private/Language/locallang_containers.xlf b/packages/vitec/Resources/Private/Language/locallang_containers.xlf index 2ae841d..1b398d4 100755 --- a/packages/vitec/Resources/Private/Language/locallang_containers.xlf +++ b/packages/vitec/Resources/Private/Language/locallang_containers.xlf @@ -178,6 +178,72 @@ 5 + + Background Image + + + Background Image + + + Optional image behind the whole container. Leave empty for no background image. + + + + Background Size + + + Cover (fill, may crop) + + + Contain (fit, may letterbox) + + + Stretch (distorts the image) + + + Auto (original size) + + + Custom (use Size % below) + + + Size % + + + Only used when Background Size is set to Custom. 100 = original width, 50 = half, 200 = double. + + + + Background Position + + + Top Left + + + Top Center + + + Top Right + + + Center Left + + + Centered + + + Center Right + + + Bottom Left + + + Bottom Center + + + Bottom Right + + diff --git a/packages/vitec/ext_tables.sql b/packages/vitec/ext_tables.sql index dde4e9a..4f59a59 100755 --- a/packages/vitec/ext_tables.sql +++ b/packages/vitec/ext_tables.sql @@ -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 ); diff --git a/public/.htaccess b/public/.htaccess index 26bc443..6dac20a 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -397,10 +397,10 @@ FileETag None # Add your own rules here. -AuthUserFile "/usr/www/users/vitecevo/live/public/.htpasswd" -AuthName "DEV" -AuthType Basic - - Require ip 217.70.192.164 - Require valid-user - +#AuthUserFile "/usr/www/users/vitecevo/live/public/.htpasswd" +#AuthName "DEV" +#AuthType Basic +# + #Require ip 217.70.192.164 + #Require valid-user +# diff --git a/public/_frontend/index.html b/public/_frontend/index.html index f32b80b..d919d2c 100644 --- a/public/_frontend/index.html +++ b/public/_frontend/index.html @@ -25,8 +25,8 @@ VITEC - - + +