Work-Commit

This commit is contained in:
khaccount
2026-06-05 10:37:21 +02:00
parent 409afc2433
commit 64ea55d29d
47 changed files with 6547 additions and 0 deletions

View File

0
packages/vitec/Classes/Domain/Model/Product.php Normal file → Executable file
View File

View File

@@ -0,0 +1,922 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Domain\Model;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Domain\Model\Category;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
/**
* This file is part of the "VITEC" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* (c) 2025
*/
/**
* Product
*/
class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
/**
* title
*
* @var string
* @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty")
*/
protected $title;
/**
* slug
*
* @var string
*/
protected $slug;
/**
* urltitle
*
* @var string
*/
protected $urltitle = '';
/**
* seotitle
*
* @var string
*/
protected $seotitle = '';
/**
* seometa
*
* @var string
*/
protected $seometa = '';
/**
* keywords
*
* @var string
*/
protected $keywords = '';
/**
* structureddata
*
* @var string
*/
protected $structureddata = '';
/**
* teaser
*
* @var string
*/
protected $teaser = '';
/**
* subtitle
*
* @var string
*/
protected $subtitle = '';
/**
* video
*
* @var string
*/
protected $video = '';
/**
* hideonapp
*
* @var bool
*/
protected $hideonapp = false;
/**
* hideonwebsite
*
* @var bool
*/
protected $hideonwebsite = false;
/**
* hideondatasheets
*
* @var bool
*/
protected $hideondatasheets = false;
/**
* hideonproducts
*
* @var bool
*/
protected $hideonproducts = false;
/**
* applications
*
* @var string
*/
protected $applications = '';
/**
* description
*
* @var string
*/
protected $description = '';
/**
* highlights
*
* @var string
*/
protected $highlights = '';
/**
* shortcut
*
* @var bool
*/
protected $shortcut = false;
/**
* shortcutpid
*
* @var string
*/
protected $shortcutpid = '';
/**
* legacy
*
* @var bool
*/
protected $legacy = false;
/**
* supportproduct
*
* @var bool
*/
protected $supportproduct = false;
/**
* subproduct
*
* @var bool
*/
protected $subproduct = false;
/**
* @var ObjectStorage<Category>
*/
protected $categories;
/**
* Product images
*
* @var ObjectStorage<FileReference>
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
*/
protected $productimage;
/**
* Downloads
*
* @var ObjectStorage<\Evomedien\Vitec\Domain\Model\Download>
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
*/
protected $downloads;
/**
* Open Graph Image
*
* @var FileReference
*/
protected $ogimage;
/**
* Content element link - Key Features
*
* @var string
*/
protected $contentelement;
/**
* Content element link - CTA Features
*
* @var string
*/
protected $contentelementcta;
/**
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Evomedien\Vitec\Domain\Model\Product>
* @TYPO3\CMS\Extbase\Annotation\ORM\Lazy
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
*/
protected $relatedprodukt;
/* --------------------------------------------------------------------- */
/**
* Returns the title
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Sets the title
*
* @param string $title
* @return void
*/
public function setTitle(string $title)
{
$this->title = $title;
}
/**
* Returns the video
*
* @return string
*/
public function getVideo()
{
return $this->video;
}
/**
* Sets the video
*
* @param string $video
* @return void
*/
public function setVideo(string $video)
{
$this->video = $video;
}
/**
* Returns the slug
*
* @return string
*/
public function getSlug()
{
return $this->slug;
}
/**
* Sets the slug
*
* @param string $slug
* @return void
*/
public function setSlug(string $slug)
{
$this->slug = $slug;
}
/**
* Returns the urltitle
*
* @return string
*/
public function getUrltitle()
{
return $this->urltitle;
}
/**
* Sets the urltitle
*
* @param string $urltitle
* @return void
*/
public function setUrltitle(string $urltitle)
{
$this->urltitle = $urltitle;
}
/**
* Returns the seotitle
*
* @return string
*/
public function getSeotitle()
{
return $this->seotitle;
}
/**
* Sets the seotitle
*
* @param string $seotitle
* @return void
*/
public function setSeotitle(string $seotitle)
{
$this->seotitle = $seotitle;
}
/**
* Returns the seometa
*
* @return string $seometa
*/
public function getSeometa()
{
return $this->seometa;
}
/**
* Sets the seometa
*
* @param string $seometa
* @return void
*/
public function setSeometa($seometa)
{
$this->seometa = $seometa;
}
/**
* Returns the keywords
*
* @return string $keywords
*/
public function getKeywords()
{
return $this->keywords;
}
/**
* Sets the keywords
*
* @param string $keywords
* @return void
*/
public function setKeywords($keywords)
{
$this->keywords = $keywords;
}
/**
* Returns the teaser
*
* @return string $kteasereywords
*/
public function getTeaser()
{
return $this->teaser;
}
/**
* Sets the teaser
*
* @param string $teaser
* @return void
*/
public function setTeaser($teaser)
{
$this->teaser = $teaser;
}
/**
* Returns the subtitle
*
* @return string $subtitle
*/
public function getSubtitle()
{
return $this->subtitle;
}
/**
* Sets the subtitle
*
* @param string $subtitle
* @return void
*/
public function setSubtitle($subtitle)
{
$this->subtitle = $subtitle;
}
/**
* Returns the hideonapp
*
* @return bool $hideonapp
*/
public function getHideonapp()
{
return $this->hideonapp;
}
/**
* Sets the hideonapp
*
* @param bool $hideonapp
* @return void
*/
public function setHideonapp($hideonapp)
{
$this->hideonapp = $hideonapp;
}
/**
* Returns the description
*
* @return bool $description
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the description
*
* @param bool $description
* @return void
*/
public function setDescriptionp($description)
{
$this->description = $description;
}
/**
* Returns the hideonwebsite
*
* @return bool $hideonwebsite
*/
public function getHideonwebsite()
{
return $this->hideonwebsite;
}
/**
* Sets the hideonwebsite
*
* @param bool $hideonwebsite
* @return void
*/
public function setHideonwebsite($hideonwebsite)
{
$this->hideonwebsite = $hideonwebsite;
}
/**
* Returns the hideondatasheets
*
* @return bool $hideondatasheets
*/
public function getHideondatasheets()
{
return $this->hideondatasheets;
}
/**
* Sets the hideondatasheets
*
* @param bool $hideondatasheets
* @return void
*/
public function setHideondatasheets($hideondatasheets)
{
$this->hideondatasheets = $hideondatasheets;
}
/**
* Returns the hideonproducts
*
* @return bool $hideonproducts
*/
public function getHideonproducts()
{
return $this->hideonproducts;
}
/**
* Sets the hideonproducts
*
* @param bool $hideonproducts
* @return void
*/
public function setHideonproducts($hideonproducts)
{
$this->hideonproducts = $hideonproducts;
}
/**
* Returns the structureddata
*
* @return string $structureddata
*/
public function getStructureddata()
{
return $this->structureddata;
}
/**
* Sets the structureddata
*
* @param string $structureddata
* @return void
*/
public function setStructureddata($structureddata)
{
$this->structureddata = $structureddata;
}
/**
* Returns the applications
*
* @return string $applications
*/
public function getApplications()
{
return $this->applications;
}
/**
* Sets the applications
*
* @param string $applications
* @return void
*/
public function setApplications($applications)
{
$this->applications = $applications;
}
/**
* Returns the highlights
*
* @return string $highlights
*/
public function getHighlights()
{
return $this->highlights;
}
/**
* Sets the highlights
*
* @param string $highlights
* @return void
*/
public function setHighlights($highlights)
{
$this->highlights = $highlights;
}
/**
* Returns the shortcutpid
*
* @return string
*/
public function getShortcutpid()
{
return $this->shortcutpid;
}
/**
* Sets the shortcutpid
*
* @param string $shortcutpid
* @return void
*/
public function setShortcutpid ($shortcutpid)
{
$this->shortcutpid = $shortcutpid;
}
/**
* Returns the shortcut
*
* @return bool $shortcut
*/
public function getShortcut()
{
return $this->shortcut;
}
/**
* Sets the shortcut
*
* @param bool $shortcut
* @return void
*/
public function setShortcut($shortcut)
{
$this->shortcut = $shortcut;
}
/**
* Returns the legacy
*
* @return bool $legacy
*/
public function getLegacy()
{
return $this->legacy;
}
/**
* Sets the legacy
*
* @param bool $legacy
* @return void
*/
public function setLegacy($legacy)
{
$this->legacy = $legacy;
}
/**
* Returns the supportproduct
*
* @return bool $supportproduct
*/
public function getSupportproduct()
{
return $this->supportproduct;
}
/**
* Sets the supportproduct
*
* @param bool $supportproduct
* @return void
*/
public function setSupportproduct($supportproduct)
{
$this->supportproduct = $supportproduct;
}
/**
* Returns the subproduct
*
* @return bool $subproduct
*/
public function getSubproduct()
{
return $this->subproduct;
}
/**
* Sets the subproduct
*
* @param bool $subproduct
* @return void
*/
public function setSubproduct($subproduct)
{
$this->subproduct = $subproduct;
}
/**
* Initializes the ObjectStorage for categories
*/
public function __construct()
{
$this->categories = new ObjectStorage();
$this->productimage = new ObjectStorage();
$this->downloads = new ObjectStorage();
}
/**
* Adds a category
*
* @param Category $category
* @return void
*/
public function addCategory(Category $category)
{
$this->categories->attach($category);
}
/**
* Removes a category
*
* @param Category $categoryToRemove
* @return void
*/
public function removeCategory(Category $categoryToRemove)
{
$this->categories->detach($categoryToRemove);
}
/**
* Returns the categories
*
* @return ObjectStorage<Category>
*/
public function getCategories()
{
return $this->categories;
}
/**
* Sets the categories
*
* @param ObjectStorage<Category> $categories
* @return void
*/
public function setCategories(ObjectStorage $categories)
{
$this->categories = $categories;
}
/**
* Adds a product image
*
* @param FileReference $productimage
* @return void
*/
public function addProductimage(FileReference $productimage)
{
$this->productimage->attach($productimage);
}
/**
* Removes a product image
*
* @param FileReference $productimageToRemove
* @return void
*/
public function removeProductimage(FileReference $productimageToRemove)
{
$this->productimage->detach($productimageToRemove);
}
/**
* Returns the product images
*
* @return ObjectStorage<FileReference>
*/
public function getProductimage()
{
return $this->productimage;
}
/**
* Sets the product images
*
* @param ObjectStorage<FileReference> $productimage
* @return void
*/
public function setProductimage(ObjectStorage $productimage)
{
$this->productimage = $productimage;
}
/**
* Adds a download
*
* @param \Evomedien\Vitec\Domain\Model\Download $download
* @return void
*/
public function addDownload(\Evomedien\Vitec\Domain\Model\Download $download): void
{
$this->downloads->attach($download);
}
/**
* Removes a download
*
* @param \Evomedien\Vitec\Domain\Model\Download $downloadToRemove
* @return void
*/
public function removeDownload(\Evomedien\Vitec\Domain\Model\Download $downloadToRemove): void
{
$this->downloads->detach($downloadToRemove);
}
/**
* Returns the downloads
*
* @return ObjectStorage<\Evomedien\Vitec\Domain\Model\Download>
*/
public function getDownloads(): ObjectStorage
{
return $this->downloads;
}
/**
* Sets the downloads
*
* @param ObjectStorage<\Evomedien\Vitec\Domain\Model\Download> $downloads
* @return void
*/
public function setDownloads(ObjectStorage $downloads): void
{
$this->downloads = $downloads;
}
/**
* Returns the Open Graph Image
*
* @return FileReference|null
*/
public function getOgimage(): ?FileReference
{
return $this->ogimage;
}
/**
* Sets the Open Graph Image
*
* @param FileReference $ogimage
* @return void
*/
public function setOgimage(FileReference $ogimage): void
{
$this->ogimage = $ogimage;
}
/**
* Returns the content element link
*
* @return string
*/
public function getContentelement(): string
{
return $this->contentelement;
}
/**
* Sets the content element link
*
* @param string $contentelement
* @return void
*/
public function setContentelement(string $contentelement): void
{
$this->contentelement = $contentelement;
}
/**
* Returns the content element cta link
*
* @return string
*/
public function getContentelementcta(): string
{
return $this->contentelementcta;
}
/**
* Sets the content element link cta
*
* @param string $contentelementcta
* @return void
*/
public function setContentelementcta(string $contentelementcta): void
{
$this->contentelementcta = $contentelementcta;
}
/**
* @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Extension\Domain\Model\Product>
*/
public function getRelatedprodukt(): \TYPO3\CMS\Extbase\Persistence\ObjectStorage
{
return $this->relatedprodukt;
}
/**
* @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Extension\Domain\Model\Product> $relatedprodukt
*/
public function setRelatedprodukt(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $relatedprodukt): void
{
$this->relatedprodukt = $relatedprodukt;
}
/* --------------------------------------------------------------------- */
}

View File

@@ -0,0 +1,952 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Domain\Model;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Domain\Model\Category;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
/**
* This file is part of the "VITEC" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* (c) 2025
*/
/**
* Product
*/
class Product extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
/**
* title
*
* @var string
* @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty")
*/
protected $title;
/**
* slug
*
* @var string
*/
protected $slug;
/**
* urltitle
*
* @var string
*/
protected $urltitle = '';
/**
* seotitle
*
* @var string
*/
protected $seotitle = '';
/**
* seometa
*
* @var string
*/
protected $seometa = '';
/**
* keywords
*
* @var string
*/
protected $keywords = '';
/**
* structureddata
*
* @var string
*/
protected $structureddata = '';
/**
* teaser
*
* @var string
*/
protected $teaser = '';
/**
* subtitle
*
* @var string
*/
protected $subtitle = '';
/**
* video
*
* @var string
*/
protected $video = '';
/**
* hideonapp
*
* @var bool
*/
protected $hideonapp = false;
/**
* hideonwebsite
*
* @var bool
*/
protected $hideonwebsite = false;
/**
* hideondatasheets
*
* @var bool
*/
protected $hideondatasheets = false;
/**
* hideonproducts
*
* @var bool
*/
protected $hideonproducts = false;
/**
* applications
*
* @var string
*/
protected $applications = '';
/**
* description
*
* @var string
*/
protected $description = '';
/**
* highlights
*
* @var string
*/
protected $highlights = '';
/**
* shortcut
*
* @var bool
*/
protected $shortcut = false;
/**
* shortcutpid
*
* @var string
*/
protected $shortcutpid = '';
/**
* legacy
*
* @var bool
*/
protected $legacy = false;
/**
* supportproduct
*
* @var bool
*/
protected $supportproduct = false;
/**
* subproduct
*
* @var bool
*/
protected $subproduct = false;
/**
* @var ObjectStorage<Category>
*/
protected $categories;
/**
* Product images
*
* @var ObjectStorage<FileReference>
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
*/
protected $productimage;
/**
* Downloads
*
* @var ObjectStorage<\Evomedien\Vitec\Domain\Model\Download>
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
*/
protected $downloads;
/**
* Open Graph Image
*
* @var FileReference
*/
protected $ogimage;
/**
* Content element link - Key Features
*
* @var string
*/
/**
* Uploaded / locally selected video file (FAL)
*
* @var \TYPO3\CMS\Extbase\Domain\Model\FileReference|null
*/
protected $videofile;
protected $contentelement;
/**
* Content element link - CTA Features
*
* @var string
*/
protected $contentelementcta;
/**
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Evomedien\Vitec\Domain\Model\Product>
* @TYPO3\CMS\Extbase\Annotation\ORM\Lazy
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
*/
protected $relatedprodukt;
/* --------------------------------------------------------------------- */
/**
* Returns the title
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Sets the title
*
* @param string $title
* @return void
*/
public function setTitle(string $title)
{
$this->title = $title;
}
/**
* Returns the video
*
* @return string
*/
public function getVideo()
{
return $this->video;
}
/**
* Sets the video
*
* @param string $video
* @return void
*/
public function setVideo(string $video)
{
$this->video = $video;
}
/**
* Returns the slug
*
* @return string
*/
public function getSlug()
{
return $this->slug;
}
/**
* Sets the slug
*
* @param string $slug
* @return void
*/
public function setSlug(string $slug)
{
$this->slug = $slug;
}
/**
* Returns the urltitle
*
* @return string
*/
public function getUrltitle()
{
return $this->urltitle;
}
/**
* Sets the urltitle
*
* @param string $urltitle
* @return void
*/
public function setUrltitle(string $urltitle)
{
$this->urltitle = $urltitle;
}
/**
* Returns the seotitle
*
* @return string
*/
public function getSeotitle()
{
return $this->seotitle;
}
/**
* Sets the seotitle
*
* @param string $seotitle
* @return void
*/
public function setSeotitle(string $seotitle)
{
$this->seotitle = $seotitle;
}
/**
* Returns the seometa
*
* @return string $seometa
*/
public function getSeometa()
{
return $this->seometa;
}
/**
* Sets the seometa
*
* @param string $seometa
* @return void
*/
public function setSeometa($seometa)
{
$this->seometa = $seometa;
}
/**
* Returns the keywords
*
* @return string $keywords
*/
public function getKeywords()
{
return $this->keywords;
}
/**
* Sets the keywords
*
* @param string $keywords
* @return void
*/
public function setKeywords($keywords)
{
$this->keywords = $keywords;
}
/**
* Returns the teaser
*
* @return string $kteasereywords
*/
public function getTeaser()
{
return $this->teaser;
}
/**
* Sets the teaser
*
* @param string $teaser
* @return void
*/
public function setTeaser($teaser)
{
$this->teaser = $teaser;
}
/**
* Returns the subtitle
*
* @return string $subtitle
*/
public function getSubtitle()
{
return $this->subtitle;
}
/**
* Sets the subtitle
*
* @param string $subtitle
* @return void
*/
public function setSubtitle($subtitle)
{
$this->subtitle = $subtitle;
}
/**
* Returns the hideonapp
*
* @return bool $hideonapp
*/
public function getHideonapp()
{
return $this->hideonapp;
}
/**
* Sets the hideonapp
*
* @param bool $hideonapp
* @return void
*/
public function setHideonapp($hideonapp)
{
$this->hideonapp = $hideonapp;
}
/**
* Returns the description
*
* @return bool $description
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the description
*
* @param bool $description
* @return void
*/
public function setDescriptionp($description)
{
$this->description = $description;
}
/**
* Returns the hideonwebsite
*
* @return bool $hideonwebsite
*/
public function getHideonwebsite()
{
return $this->hideonwebsite;
}
/**
* Sets the hideonwebsite
*
* @param bool $hideonwebsite
* @return void
*/
public function setHideonwebsite($hideonwebsite)
{
$this->hideonwebsite = $hideonwebsite;
}
/**
* Returns the hideondatasheets
*
* @return bool $hideondatasheets
*/
public function getHideondatasheets()
{
return $this->hideondatasheets;
}
/**
* Sets the hideondatasheets
*
* @param bool $hideondatasheets
* @return void
*/
public function setHideondatasheets($hideondatasheets)
{
$this->hideondatasheets = $hideondatasheets;
}
/**
* Returns the hideonproducts
*
* @return bool $hideonproducts
*/
public function getHideonproducts()
{
return $this->hideonproducts;
}
/**
* Sets the hideonproducts
*
* @param bool $hideonproducts
* @return void
*/
public function setHideonproducts($hideonproducts)
{
$this->hideonproducts = $hideonproducts;
}
/**
* Returns the structureddata
*
* @return string $structureddata
*/
public function getStructureddata()
{
return $this->structureddata;
}
/**
* Sets the structureddata
*
* @param string $structureddata
* @return void
*/
public function setStructureddata($structureddata)
{
$this->structureddata = $structureddata;
}
/**
* Returns the applications
*
* @return string $applications
*/
public function getApplications()
{
return $this->applications;
}
/**
* Sets the applications
*
* @param string $applications
* @return void
*/
public function setApplications($applications)
{
$this->applications = $applications;
}
/**
* Returns the highlights
*
* @return string $highlights
*/
public function getHighlights()
{
return $this->highlights;
}
/**
* Sets the highlights
*
* @param string $highlights
* @return void
*/
public function setHighlights($highlights)
{
$this->highlights = $highlights;
}
/**
* Returns the shortcutpid
*
* @return string
*/
public function getShortcutpid()
{
return $this->shortcutpid;
}
/**
* Sets the shortcutpid
*
* @param string $shortcutpid
* @return void
*/
public function setShortcutpid ($shortcutpid)
{
$this->shortcutpid = $shortcutpid;
}
/**
* Returns the shortcut
*
* @return bool $shortcut
*/
public function getShortcut()
{
return $this->shortcut;
}
/**
* Sets the shortcut
*
* @param bool $shortcut
* @return void
*/
public function setShortcut($shortcut)
{
$this->shortcut = $shortcut;
}
/**
* Returns the legacy
*
* @return bool $legacy
*/
public function getLegacy()
{
return $this->legacy;
}
/**
* Sets the legacy
*
* @param bool $legacy
* @return void
*/
public function setLegacy($legacy)
{
$this->legacy = $legacy;
}
/**
* Returns the supportproduct
*
* @return bool $supportproduct
*/
public function getSupportproduct()
{
return $this->supportproduct;
}
/**
* Sets the supportproduct
*
* @param bool $supportproduct
* @return void
*/
public function setSupportproduct($supportproduct)
{
$this->supportproduct = $supportproduct;
}
/**
* Returns the subproduct
*
* @return bool $subproduct
*/
public function getSubproduct()
{
return $this->subproduct;
}
/**
* Sets the subproduct
*
* @param bool $subproduct
* @return void
*/
public function setSubproduct($subproduct)
{
$this->subproduct = $subproduct;
}
/**
* Initializes the ObjectStorage for categories
*/
public function __construct()
{
$this->categories = new ObjectStorage();
$this->productimage = new ObjectStorage();
$this->downloads = new ObjectStorage();
}
/**
* Adds a category
*
* @param Category $category
* @return void
*/
public function addCategory(Category $category)
{
$this->categories->attach($category);
}
/**
* Removes a category
*
* @param Category $categoryToRemove
* @return void
*/
public function removeCategory(Category $categoryToRemove)
{
$this->categories->detach($categoryToRemove);
}
/**
* Returns the categories
*
* @return ObjectStorage<Category>
*/
public function getCategories()
{
return $this->categories;
}
/**
* Sets the categories
*
* @param ObjectStorage<Category> $categories
* @return void
*/
public function setCategories(ObjectStorage $categories)
{
$this->categories = $categories;
}
/**
* Adds a product image
*
* @param FileReference $productimage
* @return void
*/
public function addProductimage(FileReference $productimage)
{
$this->productimage->attach($productimage);
}
/**
* Removes a product image
*
* @param FileReference $productimageToRemove
* @return void
*/
public function removeProductimage(FileReference $productimageToRemove)
{
$this->productimage->detach($productimageToRemove);
}
/**
* Returns the product images
*
* @return ObjectStorage<FileReference>
*/
public function getProductimage()
{
return $this->productimage;
}
/**
* Sets the product images
*
* @param ObjectStorage<FileReference> $productimage
* @return void
*/
public function setProductimage(ObjectStorage $productimage)
{
$this->productimage = $productimage;
}
/**
* Adds a download
*
* @param \Evomedien\Vitec\Domain\Model\Download $download
* @return void
*/
public function addDownload(\Evomedien\Vitec\Domain\Model\Download $download): void
{
$this->downloads->attach($download);
}
/**
* Removes a download
*
* @param \Evomedien\Vitec\Domain\Model\Download $downloadToRemove
* @return void
*/
public function removeDownload(\Evomedien\Vitec\Domain\Model\Download $downloadToRemove): void
{
$this->downloads->detach($downloadToRemove);
}
/**
* Returns the downloads
*
* @return ObjectStorage<\Evomedien\Vitec\Domain\Model\Download>
*/
public function getDownloads(): ObjectStorage
{
return $this->downloads;
}
/**
* Sets the downloads
*
* @param ObjectStorage<\Evomedien\Vitec\Domain\Model\Download> $downloads
* @return void
*/
public function setDownloads(ObjectStorage $downloads): void
{
$this->downloads = $downloads;
}
/**
* Returns the Open Graph Image
*
* @return FileReference|null
*/
public function getOgimage(): ?FileReference
{
return $this->ogimage;
}
/**
* Sets the Open Graph Image
*
* @param FileReference $ogimage
* @return void
*/
public function setOgimage(FileReference $ogimage): void
{
$this->ogimage = $ogimage;
}
/**
* Returns the content element link
*
* @return string
*/
/**
* Returns the uploaded video file
*
* @return \TYPO3\CMS\Extbase\Domain\Model\FileReference|null
*/
public function getVideofile(): ?\TYPO3\CMS\Extbase\Domain\Model\FileReference
{
return $this->videofile;
}
/**
* Sets the uploaded video file
*
* @param \TYPO3\CMS\Extbase\Domain\Model\FileReference|null $videofile
* @return void
*/
public function setVideofile(?\TYPO3\CMS\Extbase\Domain\Model\FileReference $videofile): void
{
$this->videofile = $videofile;
}
public function getContentelement(): string
{
return $this->contentelement;
}
/**
* Sets the content element link
*
* @param string $contentelement
* @return void
*/
public function setContentelement(string $contentelement): void
{
$this->contentelement = $contentelement;
}
/**
* Returns the content element cta link
*
* @return string
*/
public function getContentelementcta(): string
{
return $this->contentelementcta;
}
/**
* Sets the content element link cta
*
* @param string $contentelementcta
* @return void
*/
public function setContentelementcta(string $contentelementcta): void
{
$this->contentelementcta = $contentelementcta;
}
/**
* @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Extension\Domain\Model\Product>
*/
public function getRelatedprodukt(): \TYPO3\CMS\Extbase\Persistence\ObjectStorage
{
return $this->relatedprodukt;
}
/**
* @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Extension\Domain\Model\Product> $relatedprodukt
*/
public function setRelatedprodukt(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $relatedprodukt): void
{
$this->relatedprodukt = $relatedprodukt;
}
/* --------------------------------------------------------------------- */
}

View File

0
packages/vitec/Classes/Hook/SyncBackendLayoutHook.php Normal file → Executable file
View File

View File

View File

@@ -0,0 +1,238 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Service;
use Doctrine\DBAL\ParameterType;
use Evomedien\Vitec\UserFunc\ProductListJsonRenderer;
use Evomedien\Vitec\UserFunc\ProductShowJsonRenderer;
use Evomedien\Vitec\UserFunc\UsecaseListJsonRenderer;
use Evomedien\Vitec\UserFunc\UsecaseShowJsonRenderer;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Resolves a TYPO3 typolink string (as stored by `inputLink` fields like
* Product.contentelement / Product.contentelementcta) to the JSON
* representation of the referenced tt_content element.
*
* Mirrors the shape produced by
* {@see \Evomedien\Vitec\DataProcessing\ContainerChildrenProcessor}:
* { id, type, colPos, sorting, appearance, data }
*
* Nested VITEC list-plugins are resolved through their `renderForRecord()`
* just like container children, so a referenced productlist / productshow /
* usecaselist / usecaseshow appears with its full headless JSON in `data`.
*
* Exception-safe: every public entry point returns `null` on any failure
* so the surrounding JSON output stays clean.
*/
final class ContentElementResolver
{
/** 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 = [
'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',
'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',
];
private const KEEP_IF_ZERO = ['header_layout'];
private const PLUGIN_RENDERERS = [
'vitec_productlist' => [ProductListJsonRenderer::class, 'products'],
'vitec_productshow' => [ProductShowJsonRenderer::class, 'product'],
'vitec_usecaselist' => [UsecaseListJsonRenderer::class, 'usecases'],
'vitec_usecaseshow' => [UsecaseShowJsonRenderer::class, 'usecase'],
];
/**
* Resolve a typolink string to a normalised tt_content JSON element.
*
* Accepted forms:
* - "t3://record?identifier=tt_content&uid=N"
* - "<numeric>" (legacy: bare tt_content uid)
* - everything else → null (page links etc.)
*
* @return array<string,mixed>|null
*/
public static function resolveLink(?string $link): ?array
{
try {
$link = trim((string)$link);
if ($link === '') {
return null;
}
$uid = self::extractTtContentUid($link);
if ($uid <= 0) {
return null;
}
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$row = $qb
->select('*')
->from('tt_content')
->where(
$qb->expr()->eq('uid', $qb->createNamedParameter($uid, ParameterType::INTEGER)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
if (!$row) {
return null;
}
return self::normaliseRecord($row);
} catch (\Throwable $e) {
return null;
}
}
/**
* Normalise a tt_content DB row to the same envelope shape that
* {@see \Evomedien\Vitec\DataProcessing\ContainerChildrenProcessor}
* emits for container children. VITEC list-plugin children are
* resolved to their headless JSON.
*
* @param array<string,mixed> $record
* @return array<string,mixed>
*/
public static function normaliseRecord(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 (self::isEmpty($value) && !in_array($field, self::KEEP_IF_ZERO, true)) {
continue;
}
$data[$field] = self::castValue($field, $value);
}
self::resolvePluginData($record, $data);
return [
'id' => (int)$record['uid'],
'type' => (string)$record['CType'],
'colPos' => (int)($record['colPos'] ?? 0),
'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,
];
}
/**
* Extract the tt_content uid from a typolink string.
*/
private static function extractTtContentUid(string $link): int
{
if (ctype_digit($link)) {
return (int)$link;
}
if (!str_starts_with($link, 't3://record')) {
return 0;
}
$parts = parse_url($link);
if (empty($parts['query'])) {
return 0;
}
$params = [];
parse_str((string)$parts['query'], $params);
$identifier = (string)($params['identifier'] ?? '');
$uid = (int)($params['uid'] ?? 0);
if ($identifier !== 'tt_content' || $uid <= 0) {
return 0;
}
return $uid;
}
/**
* If the record is a VITEC list-plugin, run its renderer for THIS row
* and inject the decoded result under its key. Drops raw pi_flexform.
*
* @param array<string,mixed> $record
* @param array<string,mixed> $data
*/
private static function resolvePluginData(array $record, array &$data): void
{
$listType = (string)($record['list_type'] ?? '');
if ($listType === '' || !isset(self::PLUGIN_RENDERERS[$listType])) {
return;
}
try {
[$rendererClass, $jsonKey] = self::PLUGIN_RENDERERS[$listType];
$renderer = GeneralUtility::makeInstance($rendererClass);
if (!method_exists($renderer, 'renderForRecord')) {
return;
}
$json = $renderer->renderForRecord($record);
if ($json === '' || $json === null) {
return;
}
$decoded = json_decode($json, true);
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
return;
}
$data[$jsonKey] = $decoded;
unset($data['pi_flexform']);
} catch (\Throwable $e) {
// leave raw data on failure
}
}
private static function isEmpty(mixed $value): bool
{
return $value === null || $value === '' || $value === 0 || $value === '0';
}
private static function castValue(string $field, mixed $value): mixed
{
if (in_array($field, ['header_layout'], true)) {
return (int)$value;
}
return $value;
}
}

View File

View File

@@ -0,0 +1,463 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use Doctrine\DBAL\ParameterType;
use Evomedien\Vitec\Domain\Repository\ProductRepository;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
/**
* UserFunc to render product list as JSON for headless.
*
* `render()` performs page discovery (used for top-level list plugins via
* TypoScript). `renderForRecord()` processes one specific tt_content row and
* is used both internally and by ContainerChildrenProcessor to resolve a
* product-list plugin nested inside a b13 container.
*/
class ProductListJsonRenderer
{
public function render(string $content, array $conf): string
{
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$contentElements = $queryBuilder
->select('*')
->from('tt_content')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter('vitec_productlist', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if (empty($contentElements)) {
// Not a product-list page: emit nothing so headless removes the key.
return '';
}
return $this->renderForRecord($contentElements[0]);
}
/**
* Render exactly the given tt_content row (the product-list plugin element).
* Exception-safe: returns '' on any failure.
*
* @param array<string,mixed> $contentElement tt_content row of the plugin
*/
public function renderForRecord(array $contentElement): string
{
try {
// Parse FlexForm of THIS element
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$categoryUids = array_filter(
array_map('intval', explode(',', (string)($settings['categories'] ?? '')))
);
$debugMode = (bool)($settings['debug'] ?? false);
$allProducts = (bool)($settings['allproducts'] ?? false);
// Extbase repositories don't work in UserFunc context — direct query.
$productQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$productQuery = $productQueryBuilder
->select('p.*')
->from('tx_vitec_domain_model_product', 'p')
->where(
$productQueryBuilder->expr()->eq('p.deleted', 0),
$productQueryBuilder->expr()->eq('p.hidden', 0),
$productQueryBuilder->expr()->eq('p.legacy', 0),
$productQueryBuilder->expr()->eq('p.supportproduct', 0),
$productQueryBuilder->expr()->eq('p.hideonwebsite', 0),
$productQueryBuilder->expr()->eq('p.hideonproducts', 0),
$productQueryBuilder->expr()->eq('p.subproduct', 0)
);
if (!empty($categoryUids) && !$allProducts) {
$productQuery
->join(
'p',
'sys_category_record_mm',
'mm',
'mm.uid_foreign = p.uid AND mm.tablenames = ' . $productQueryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) . ' AND mm.fieldname = ' . $productQueryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->andWhere(
$productQueryBuilder->expr()->in('mm.uid_local', $productQueryBuilder->createNamedParameter($categoryUids, Connection::PARAM_INT_ARRAY))
)
->groupBy('p.uid');
}
$products = $productQuery->executeQuery()->fetchAllAssociative();
$productsData = [];
foreach ($products as $product) {
$productsData[] = $this->serializeProduct($product);
}
if ($debugMode) {
return json_encode([
'products' => $productsData,
'debug' => [
'pageId' => (int)($GLOBALS['TSFE']->id ?? 0),
'categoryUids' => $categoryUids,
'productCount' => count($productsData),
'settings' => $settings,
],
]);
}
return json_encode($productsData);
} catch (\Throwable $e) {
return '';
}
}
/**
* Serialize a single product DB row to the full headless JSON structure.
*
* @param array<string,mixed> $product
* @return array<string,mixed>
*/
protected function serializeProduct(array $product): array
{
$uid = (int)$product['uid'];
return [
'uid' => $uid,
'title' => (string)($product['title'] ?? ''),
'slug' => (string)($product['slug'] ?? ''),
'urltitle' => (string)($product['urltitle'] ?? ''),
'seotitle' => (string)($product['seotitle'] ?? ''),
'seometa' => (string)($product['seometa'] ?? ''),
'keywords' => (string)($product['keywords'] ?? ''),
'structureddata' => (string)($product['structureddata'] ?? ''),
'teaser' => (string)($product['teaser'] ?? ''),
'subtitle' => (string)($product['subtitle'] ?? ''),
'video' => (string)($product['video'] ?? ''),
'applications' => (string)($product['applications'] ?? ''),
'description' => (string)($product['description'] ?? ''),
'highlights' => (string)($product['highlights'] ?? ''),
'shortcutpid' => (string)($product['shortcutpid'] ?? ''),
'contentelement' => (string)($product['contentelement'] ?? ''),
'contentelementcta' => (string)($product['contentelementcta'] ?? ''),
'hideonapp' => (bool)($product['hideonapp'] ?? false),
'hideonwebsite' => (bool)($product['hideonwebsite'] ?? false),
'hideondatasheets' => (bool)($product['hideondatasheets'] ?? false),
'hideonproducts' => (bool)($product['hideonproducts'] ?? false),
'shortcut' => (bool)($product['shortcut'] ?? false),
'legacy' => (bool)($product['legacy'] ?? false),
'supportproduct' => (bool)($product['supportproduct'] ?? false),
'subproduct' => (bool)($product['subproduct'] ?? false),
'link' => '/product/' . (string)($product['slug'] ?? ''),
'categories' => $this->getProductCategories($uid),
'images' => $this->getProductImages($uid),
'downloads' => $this->getProductDownloads($uid),
'ogimage' => $this->getProductOgImage($uid),
'relatedprodukt' => $this->getRelatedProducts($uid),
];
}
protected function getProductImages(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileReferences = $queryBuilder
->select('sfr.uid', 'sfr.uid_local', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop')
->from('sys_file_reference', 'sfr')
->where(
$queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter('productimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('sfr.deleted', 0),
$queryBuilder->expr()->eq('sfr.hidden', 0)
)
->orderBy('sfr.sorting_foreign')
->executeQuery()
->fetchAllAssociative();
$imageService = GeneralUtility::makeInstance(ImageService::class);
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$images = [];
foreach ($fileReferences as $fileRefData) {
try {
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
$sizes = [
'small' => ['width' => 400, 'height' => null],
'medium' => ['width' => 800, 'height' => null],
'large' => ['width' => 1200, 'height' => null],
'xlarge' => ['width' => 1600, 'height' => null],
];
$srcset = [];
foreach ($sizes as $sizeName => $dimensions) {
$processedImage = $imageService->applyProcessingInstructions(
$fileReference,
[
'width' => $dimensions['width'],
'height' => $dimensions['height'],
'crop' => $fileRefData['crop'] ?? null
]
);
$imageUri = $imageService->getImageUri($processedImage);
$srcset[] = [
'url' => $imageUri,
'width' => $dimensions['width'],
'descriptor' => $dimensions['width'] . 'w'
];
}
$defaultProcessed = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 800, 'crop' => $fileRefData['crop'] ?? null]
);
$images[] = [
'uid' => (int)$fileRefData['uid'],
'url' => $imageService->getImageUri($defaultProcessed),
'title' => $fileRefData['title'] ?? '',
'alternative' => $fileRefData['alternative'] ?? '',
'description' => $fileRefData['description'] ?? '',
'srcset' => $srcset,
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type')
]
];
} catch (\Exception $e) {
continue;
}
}
return $images;
}
/**
* @return array<string,mixed>|null
*/
protected function getProductOgImage(int $productUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRefData = $queryBuilder
->select('sfr.uid', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop')
->from('sys_file_reference', 'sfr')
->where(
$queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter('ogimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('sfr.deleted', 0),
$queryBuilder->expr()->eq('sfr.hidden', 0)
)
->orderBy('sfr.sorting_foreign')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$fileRefData) {
return null;
}
try {
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
$processed = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 1200, 'crop' => $fileRefData['crop'] ?? null]
);
return [
'uid' => (int)$fileRefData['uid'],
'url' => $imageService->getImageUri($processed),
'title' => $fileRefData['title'] ?? '',
'alternative' => $fileRefData['alternative'] ?? '',
'description' => $fileRefData['description'] ?? '',
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type'),
],
];
} catch (\Exception $e) {
return null;
}
}
protected function getProductCategories(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $queryBuilder
->select('c.uid', 'c.title', 'c.description')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) .
' AND mm.fieldname = ' .
$queryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('c.deleted', 0),
$queryBuilder->expr()->eq('c.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(static function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => $cat['title'] ?? '',
'description' => $cat['description'] ?? '',
];
}, $categories);
}
protected function getProductDownloads(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_download');
$downloads = $queryBuilder
->select('d.*')
->from('tx_vitec_domain_model_download', 'd')
->join(
'd',
'tx_vitec_product_download_mm',
'mm',
'mm.uid_foreign = d.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('d.deleted', 0),
$queryBuilder->expr()->eq('d.hidden', 0),
$queryBuilder->expr()->eq('d.hideonwebsite', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($downloads as $download) {
$fileInfo = null;
if (!empty($download['file'])) {
$fileQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRef = $fileQueryBuilder
->select('fr.uid', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter((int)$download['uid'], ParameterType::INTEGER)),
$fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.deleted', 0),
$fileQueryBuilder->expr()->eq('f.missing', 0)
)
->orderBy('fr.sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if ($fileRef) {
$fileInfo = [
'uid' => (int)$fileRef['file_uid'],
'name' => $fileRef['name'],
'url' => '/fileadmin' . $fileRef['identifier'],
'size' => (int)$fileRef['size'],
'extension' => $fileRef['extension'],
'mimeType' => $fileRef['mime_type'] ?? '',
];
}
}
$result[] = [
'uid' => (int)$download['uid'],
'title' => $download['title'] ?? '',
'slug' => $download['slug'] ?? '',
'teaser' => $download['teaser'] ?? '',
'description' => $download['description'] ?? '',
'keywords' => $download['keywords'] ?? '',
'icon' => $download['icon'] ?? '',
'file' => $fileInfo,
];
}
return $result;
}
protected function getRelatedProducts(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$related = $queryBuilder
->select('p.uid', 'p.title', 'p.slug', 'p.subtitle', 'p.teaser', 'p.description')
->from('tx_vitec_domain_model_product', 'p')
->join(
'p',
'tx_vitec_product_related_mm',
'mm',
'mm.uid_foreign = p.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('p.deleted', 0),
$queryBuilder->expr()->eq('p.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($related as $rel) {
$relUid = (int)$rel['uid'];
$result[] = [
'uid' => $relUid,
'title' => (string)($rel['title'] ?? ''),
'slug' => (string)($rel['slug'] ?? ''),
'subtitle' => (string)($rel['subtitle'] ?? ''),
'teaser' => (string)($rel['teaser'] ?? ''),
'description' => (string)($rel['description'] ?? ''),
'link' => '/product/' . (string)($rel['slug'] ?? ''),
'images' => $this->getProductImages($relUid),
];
}
return $result;
}
}

View File

@@ -0,0 +1,507 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use Doctrine\DBAL\ParameterType;
use Evomedien\Vitec\Domain\Repository\ProductRepository;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
/**
* UserFunc to render product list as JSON for headless.
*
* `render()` performs page discovery (used for top-level list plugins via
* TypoScript). `renderForRecord()` processes one specific tt_content row and
* is used both internally and by ContainerChildrenProcessor to resolve a
* product-list plugin nested inside a b13 container.
*/
class ProductListJsonRenderer
{
public function render(string $content, array $conf): string
{
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$contentElements = $queryBuilder
->select('*')
->from('tt_content')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter('vitec_productlist', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if (empty($contentElements)) {
// Not a product-list page: emit nothing so headless removes the key.
return '';
}
return $this->renderForRecord($contentElements[0]);
}
/**
* Render exactly the given tt_content row (the product-list plugin element).
* Exception-safe: returns '' on any failure.
*
* @param array<string,mixed> $contentElement tt_content row of the plugin
*/
public function renderForRecord(array $contentElement): string
{
try {
// Parse FlexForm of THIS element
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$categoryUids = array_filter(
array_map('intval', explode(',', (string)($settings['categories'] ?? '')))
);
$debugMode = (bool)($settings['debug'] ?? false);
$allProducts = (bool)($settings['allproducts'] ?? false);
// Extbase repositories don't work in UserFunc context — direct query.
$productQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$productQuery = $productQueryBuilder
->select('p.*')
->from('tx_vitec_domain_model_product', 'p')
->where(
$productQueryBuilder->expr()->eq('p.deleted', 0),
$productQueryBuilder->expr()->eq('p.hidden', 0),
$productQueryBuilder->expr()->eq('p.legacy', 0),
$productQueryBuilder->expr()->eq('p.supportproduct', 0),
$productQueryBuilder->expr()->eq('p.hideonwebsite', 0),
$productQueryBuilder->expr()->eq('p.hideonproducts', 0),
$productQueryBuilder->expr()->eq('p.subproduct', 0)
);
if (!empty($categoryUids) && !$allProducts) {
$productQuery
->join(
'p',
'sys_category_record_mm',
'mm',
'mm.uid_foreign = p.uid AND mm.tablenames = ' . $productQueryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) . ' AND mm.fieldname = ' . $productQueryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->andWhere(
$productQueryBuilder->expr()->in('mm.uid_local', $productQueryBuilder->createNamedParameter($categoryUids, Connection::PARAM_INT_ARRAY))
)
->groupBy('p.uid');
}
$products = $productQuery->executeQuery()->fetchAllAssociative();
$productsData = [];
foreach ($products as $product) {
$productsData[] = $this->serializeProduct($product);
}
if ($debugMode) {
return json_encode([
'products' => $productsData,
'debug' => [
'pageId' => (int)($GLOBALS['TSFE']->id ?? 0),
'categoryUids' => $categoryUids,
'productCount' => count($productsData),
'settings' => $settings,
],
]);
}
return json_encode($productsData);
} catch (\Throwable $e) {
return '';
}
}
/**
* Serialize a single product DB row to the full headless JSON structure.
*
* @param array<string,mixed> $product
* @return array<string,mixed>
*/
protected function serializeProduct(array $product): array
{
$uid = (int)$product['uid'];
return [
'uid' => $uid,
'title' => (string)($product['title'] ?? ''),
'slug' => (string)($product['slug'] ?? ''),
'urltitle' => (string)($product['urltitle'] ?? ''),
'seotitle' => (string)($product['seotitle'] ?? ''),
'seometa' => (string)($product['seometa'] ?? ''),
'keywords' => (string)($product['keywords'] ?? ''),
'structureddata' => (string)($product['structureddata'] ?? ''),
'teaser' => (string)($product['teaser'] ?? ''),
'subtitle' => (string)($product['subtitle'] ?? ''),
'video' => (string)($product['video'] ?? ''),
'applications' => (string)($product['applications'] ?? ''),
'description' => (string)($product['description'] ?? ''),
'highlights' => (string)($product['highlights'] ?? ''),
'shortcutpid' => (string)($product['shortcutpid'] ?? ''),
'contentelement' => (string)($product['contentelement'] ?? ''),
'contentelementcta' => (string)($product['contentelementcta'] ?? ''),
'hideonapp' => (bool)($product['hideonapp'] ?? false),
'hideonwebsite' => (bool)($product['hideonwebsite'] ?? false),
'hideondatasheets' => (bool)($product['hideondatasheets'] ?? false),
'hideonproducts' => (bool)($product['hideonproducts'] ?? false),
'shortcut' => (bool)($product['shortcut'] ?? false),
'legacy' => (bool)($product['legacy'] ?? false),
'supportproduct' => (bool)($product['supportproduct'] ?? false),
'subproduct' => (bool)($product['subproduct'] ?? false),
'link' => '/product/' . (string)($product['slug'] ?? ''),
'categories' => $this->getProductCategories($uid),
'images' => $this->getProductImages($uid),
'downloads' => $this->getProductDownloads($uid),
'ogimage' => $this->getProductOgImage($uid),
'videofile' => $this->getProductVideoFile($uid),
'relatedprodukt' => $this->getRelatedProducts($uid),
];
}
protected function getProductImages(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileReferences = $queryBuilder
->select('sfr.uid', 'sfr.uid_local', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop')
->from('sys_file_reference', 'sfr')
->where(
$queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter('productimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('sfr.deleted', 0),
$queryBuilder->expr()->eq('sfr.hidden', 0)
)
->orderBy('sfr.sorting_foreign')
->executeQuery()
->fetchAllAssociative();
$imageService = GeneralUtility::makeInstance(ImageService::class);
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$images = [];
foreach ($fileReferences as $fileRefData) {
try {
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
$sizes = [
'small' => ['width' => 400, 'height' => null],
'medium' => ['width' => 800, 'height' => null],
'large' => ['width' => 1200, 'height' => null],
'xlarge' => ['width' => 1600, 'height' => null],
];
$srcset = [];
foreach ($sizes as $sizeName => $dimensions) {
$processedImage = $imageService->applyProcessingInstructions(
$fileReference,
[
'width' => $dimensions['width'],
'height' => $dimensions['height'],
'crop' => $fileRefData['crop'] ?? null
]
);
$imageUri = $imageService->getImageUri($processedImage);
$srcset[] = [
'url' => $imageUri,
'width' => $dimensions['width'],
'descriptor' => $dimensions['width'] . 'w'
];
}
$defaultProcessed = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 800, 'crop' => $fileRefData['crop'] ?? null]
);
$images[] = [
'uid' => (int)$fileRefData['uid'],
'url' => $imageService->getImageUri($defaultProcessed),
'title' => $fileRefData['title'] ?? '',
'alternative' => $fileRefData['alternative'] ?? '',
'description' => $fileRefData['description'] ?? '',
'srcset' => $srcset,
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type')
]
];
} catch (\Exception $e) {
continue;
}
}
return $images;
}
/**
* @return array<string,mixed>|null
*/
protected function getProductOgImage(int $productUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRefData = $queryBuilder
->select('sfr.uid', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop')
->from('sys_file_reference', 'sfr')
->where(
$queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter('ogimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('sfr.deleted', 0),
$queryBuilder->expr()->eq('sfr.hidden', 0)
)
->orderBy('sfr.sorting_foreign')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$fileRefData) {
return null;
}
try {
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
$processed = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 1200, 'crop' => $fileRefData['crop'] ?? null]
);
return [
'uid' => (int)$fileRefData['uid'],
'url' => $imageService->getImageUri($processed),
'title' => $fileRefData['title'] ?? '',
'alternative' => $fileRefData['alternative'] ?? '',
'description' => $fileRefData['description'] ?? '',
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type'),
],
];
} catch (\Exception $e) {
return null;
}
}
/**
* Resolve the uploaded video file (single FAL reference, fieldname=videofile).
*
* @return array<string,mixed>|null
*/
protected function getProductVideoFile(int $productUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$row = $queryBuilder
->select('fr.uid', 'fr.title', 'fr.description', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$queryBuilder->expr()->eq('fr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', \Doctrine\DBAL\ParameterType::STRING)),
$queryBuilder->expr()->eq('fr.fieldname', $queryBuilder->createNamedParameter('videofile', \Doctrine\DBAL\ParameterType::STRING)),
$queryBuilder->expr()->eq('fr.uid_foreign', $queryBuilder->createNamedParameter($productUid, \Doctrine\DBAL\ParameterType::INTEGER)),
$queryBuilder->expr()->eq('fr.deleted', 0),
$queryBuilder->expr()->eq('fr.hidden', 0),
$queryBuilder->expr()->eq('f.missing', 0)
)
->orderBy('fr.sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$row) {
return null;
}
return [
'uid' => (int)$row['file_uid'],
'name' => (string)($row['name'] ?? ''),
'url' => '/fileadmin' . ($row['identifier'] ?? ''),
'size' => (int)($row['size'] ?? 0),
'extension' => (string)($row['extension'] ?? ''),
'mimeType' => (string)($row['mime_type'] ?? ''),
'title' => (string)($row['title'] ?? ''),
'description' => (string)($row['description'] ?? ''),
];
}
protected function getProductCategories(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $queryBuilder
->select('c.uid', 'c.title', 'c.description')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) .
' AND mm.fieldname = ' .
$queryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('c.deleted', 0),
$queryBuilder->expr()->eq('c.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(static function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => $cat['title'] ?? '',
'description' => $cat['description'] ?? '',
];
}, $categories);
}
protected function getProductDownloads(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_download');
$downloads = $queryBuilder
->select('d.*')
->from('tx_vitec_domain_model_download', 'd')
->join(
'd',
'tx_vitec_product_download_mm',
'mm',
'mm.uid_foreign = d.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('d.deleted', 0),
$queryBuilder->expr()->eq('d.hidden', 0),
$queryBuilder->expr()->eq('d.hideonwebsite', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($downloads as $download) {
$fileInfo = null;
if (!empty($download['file'])) {
$fileQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRef = $fileQueryBuilder
->select('fr.uid', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter((int)$download['uid'], ParameterType::INTEGER)),
$fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.deleted', 0),
$fileQueryBuilder->expr()->eq('f.missing', 0)
)
->orderBy('fr.sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if ($fileRef) {
$fileInfo = [
'uid' => (int)$fileRef['file_uid'],
'name' => $fileRef['name'],
'url' => '/fileadmin' . $fileRef['identifier'],
'size' => (int)$fileRef['size'],
'extension' => $fileRef['extension'],
'mimeType' => $fileRef['mime_type'] ?? '',
];
}
}
$result[] = [
'uid' => (int)$download['uid'],
'title' => $download['title'] ?? '',
'slug' => $download['slug'] ?? '',
'teaser' => $download['teaser'] ?? '',
'description' => $download['description'] ?? '',
'keywords' => $download['keywords'] ?? '',
'icon' => $download['icon'] ?? '',
'file' => $fileInfo,
];
}
return $result;
}
protected function getRelatedProducts(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$related = $queryBuilder
->select('p.uid', 'p.title', 'p.slug', 'p.subtitle', 'p.teaser', 'p.description')
->from('tx_vitec_domain_model_product', 'p')
->join(
'p',
'tx_vitec_product_related_mm',
'mm',
'mm.uid_foreign = p.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('p.deleted', 0),
$queryBuilder->expr()->eq('p.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($related as $rel) {
$relUid = (int)$rel['uid'];
$result[] = [
'uid' => $relUid,
'title' => (string)($rel['title'] ?? ''),
'slug' => (string)($rel['slug'] ?? ''),
'subtitle' => (string)($rel['subtitle'] ?? ''),
'teaser' => (string)($rel['teaser'] ?? ''),
'description' => (string)($rel['description'] ?? ''),
'link' => '/product/' . (string)($rel['slug'] ?? ''),
'images' => $this->getProductImages($relUid),
];
}
return $result;
}
}

View File

@@ -0,0 +1,507 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use Doctrine\DBAL\ParameterType;
use Evomedien\Vitec\Domain\Repository\ProductRepository;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
/**
* UserFunc to render product list as JSON for headless.
*
* `render()` performs page discovery (used for top-level list plugins via
* TypoScript). `renderForRecord()` processes one specific tt_content row and
* is used both internally and by ContainerChildrenProcessor to resolve a
* product-list plugin nested inside a b13 container.
*/
class ProductListJsonRenderer
{
public function render(string $content, array $conf): string
{
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$contentElements = $queryBuilder
->select('*')
->from('tt_content')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter('vitec_productlist', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if (empty($contentElements)) {
// Not a product-list page: emit nothing so headless removes the key.
return '';
}
return $this->renderForRecord($contentElements[0]);
}
/**
* Render exactly the given tt_content row (the product-list plugin element).
* Exception-safe: returns '' on any failure.
*
* @param array<string,mixed> $contentElement tt_content row of the plugin
*/
public function renderForRecord(array $contentElement): string
{
try {
// Parse FlexForm of THIS element
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$categoryUids = array_filter(
array_map('intval', explode(',', (string)($settings['categories'] ?? '')))
);
$debugMode = (bool)($settings['debug'] ?? false);
$allProducts = (bool)($settings['allproducts'] ?? false);
// Extbase repositories don't work in UserFunc context — direct query.
$productQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$productQuery = $productQueryBuilder
->select('p.*')
->from('tx_vitec_domain_model_product', 'p')
->where(
$productQueryBuilder->expr()->eq('p.deleted', 0),
$productQueryBuilder->expr()->eq('p.hidden', 0),
$productQueryBuilder->expr()->eq('p.legacy', 0),
$productQueryBuilder->expr()->eq('p.supportproduct', 0),
$productQueryBuilder->expr()->eq('p.hideonwebsite', 0),
$productQueryBuilder->expr()->eq('p.hideonproducts', 0),
$productQueryBuilder->expr()->eq('p.subproduct', 0)
);
if (!empty($categoryUids) && !$allProducts) {
$productQuery
->join(
'p',
'sys_category_record_mm',
'mm',
'mm.uid_foreign = p.uid AND mm.tablenames = ' . $productQueryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) . ' AND mm.fieldname = ' . $productQueryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->andWhere(
$productQueryBuilder->expr()->in('mm.uid_local', $productQueryBuilder->createNamedParameter($categoryUids, Connection::PARAM_INT_ARRAY))
)
->groupBy('p.uid');
}
$products = $productQuery->executeQuery()->fetchAllAssociative();
$productsData = [];
foreach ($products as $product) {
$productsData[] = $this->serializeProduct($product);
}
if ($debugMode) {
return json_encode([
'products' => $productsData,
'debug' => [
'pageId' => (int)($GLOBALS['TSFE']->id ?? 0),
'categoryUids' => $categoryUids,
'productCount' => count($productsData),
'settings' => $settings,
],
]);
}
return json_encode($productsData);
} catch (\Throwable $e) {
return '';
}
}
/**
* Serialize a single product DB row to the full headless JSON structure.
*
* @param array<string,mixed> $product
* @return array<string,mixed>
*/
protected function serializeProduct(array $product): array
{
$uid = (int)$product['uid'];
return [
'uid' => $uid,
'title' => (string)($product['title'] ?? ''),
'slug' => (string)($product['slug'] ?? ''),
'urltitle' => (string)($product['urltitle'] ?? ''),
'seotitle' => (string)($product['seotitle'] ?? ''),
'seometa' => (string)($product['seometa'] ?? ''),
'keywords' => (string)($product['keywords'] ?? ''),
'structureddata' => (string)($product['structureddata'] ?? ''),
'teaser' => (string)($product['teaser'] ?? ''),
'subtitle' => (string)($product['subtitle'] ?? ''),
'video' => (string)($product['video'] ?? ''),
'applications' => (string)($product['applications'] ?? ''),
'description' => (string)($product['description'] ?? ''),
'highlights' => (string)($product['highlights'] ?? ''),
'shortcutpid' => (string)($product['shortcutpid'] ?? ''),
'contentelement' => \Evomedien\Vitec\Service\ContentElementResolver::resolveLink((string)($product['contentelement'] ?? '')),
'contentelementcta' => \Evomedien\Vitec\Service\ContentElementResolver::resolveLink((string)($product['contentelementcta'] ?? '')),
'hideonapp' => (bool)($product['hideonapp'] ?? false),
'hideonwebsite' => (bool)($product['hideonwebsite'] ?? false),
'hideondatasheets' => (bool)($product['hideondatasheets'] ?? false),
'hideonproducts' => (bool)($product['hideonproducts'] ?? false),
'shortcut' => (bool)($product['shortcut'] ?? false),
'legacy' => (bool)($product['legacy'] ?? false),
'supportproduct' => (bool)($product['supportproduct'] ?? false),
'subproduct' => (bool)($product['subproduct'] ?? false),
'link' => '/product/' . (string)($product['slug'] ?? ''),
'categories' => $this->getProductCategories($uid),
'images' => $this->getProductImages($uid),
'downloads' => $this->getProductDownloads($uid),
'ogimage' => $this->getProductOgImage($uid),
'videofile' => $this->getProductVideoFile($uid),
'relatedprodukt' => $this->getRelatedProducts($uid),
];
}
protected function getProductImages(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileReferences = $queryBuilder
->select('sfr.uid', 'sfr.uid_local', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop')
->from('sys_file_reference', 'sfr')
->where(
$queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter('productimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('sfr.deleted', 0),
$queryBuilder->expr()->eq('sfr.hidden', 0)
)
->orderBy('sfr.sorting_foreign')
->executeQuery()
->fetchAllAssociative();
$imageService = GeneralUtility::makeInstance(ImageService::class);
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$images = [];
foreach ($fileReferences as $fileRefData) {
try {
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
$sizes = [
'small' => ['width' => 400, 'height' => null],
'medium' => ['width' => 800, 'height' => null],
'large' => ['width' => 1200, 'height' => null],
'xlarge' => ['width' => 1600, 'height' => null],
];
$srcset = [];
foreach ($sizes as $sizeName => $dimensions) {
$processedImage = $imageService->applyProcessingInstructions(
$fileReference,
[
'width' => $dimensions['width'],
'height' => $dimensions['height'],
'crop' => $fileRefData['crop'] ?? null
]
);
$imageUri = $imageService->getImageUri($processedImage);
$srcset[] = [
'url' => $imageUri,
'width' => $dimensions['width'],
'descriptor' => $dimensions['width'] . 'w'
];
}
$defaultProcessed = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 800, 'crop' => $fileRefData['crop'] ?? null]
);
$images[] = [
'uid' => (int)$fileRefData['uid'],
'url' => $imageService->getImageUri($defaultProcessed),
'title' => $fileRefData['title'] ?? '',
'alternative' => $fileRefData['alternative'] ?? '',
'description' => $fileRefData['description'] ?? '',
'srcset' => $srcset,
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type')
]
];
} catch (\Exception $e) {
continue;
}
}
return $images;
}
/**
* @return array<string,mixed>|null
*/
protected function getProductOgImage(int $productUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRefData = $queryBuilder
->select('sfr.uid', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop')
->from('sys_file_reference', 'sfr')
->where(
$queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter('ogimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('sfr.deleted', 0),
$queryBuilder->expr()->eq('sfr.hidden', 0)
)
->orderBy('sfr.sorting_foreign')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$fileRefData) {
return null;
}
try {
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
$processed = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 1200, 'crop' => $fileRefData['crop'] ?? null]
);
return [
'uid' => (int)$fileRefData['uid'],
'url' => $imageService->getImageUri($processed),
'title' => $fileRefData['title'] ?? '',
'alternative' => $fileRefData['alternative'] ?? '',
'description' => $fileRefData['description'] ?? '',
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type'),
],
];
} catch (\Exception $e) {
return null;
}
}
/**
* Resolve the uploaded video file (single FAL reference, fieldname=videofile).
*
* @return array<string,mixed>|null
*/
protected function getProductVideoFile(int $productUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$row = $queryBuilder
->select('fr.uid', 'fr.title', 'fr.description', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$queryBuilder->expr()->eq('fr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', \Doctrine\DBAL\ParameterType::STRING)),
$queryBuilder->expr()->eq('fr.fieldname', $queryBuilder->createNamedParameter('videofile', \Doctrine\DBAL\ParameterType::STRING)),
$queryBuilder->expr()->eq('fr.uid_foreign', $queryBuilder->createNamedParameter($productUid, \Doctrine\DBAL\ParameterType::INTEGER)),
$queryBuilder->expr()->eq('fr.deleted', 0),
$queryBuilder->expr()->eq('fr.hidden', 0),
$queryBuilder->expr()->eq('f.missing', 0)
)
->orderBy('fr.sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$row) {
return null;
}
return [
'uid' => (int)$row['file_uid'],
'name' => (string)($row['name'] ?? ''),
'url' => '/fileadmin' . ($row['identifier'] ?? ''),
'size' => (int)($row['size'] ?? 0),
'extension' => (string)($row['extension'] ?? ''),
'mimeType' => (string)($row['mime_type'] ?? ''),
'title' => (string)($row['title'] ?? ''),
'description' => (string)($row['description'] ?? ''),
];
}
protected function getProductCategories(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $queryBuilder
->select('c.uid', 'c.title', 'c.description')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) .
' AND mm.fieldname = ' .
$queryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('c.deleted', 0),
$queryBuilder->expr()->eq('c.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(static function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => $cat['title'] ?? '',
'description' => $cat['description'] ?? '',
];
}, $categories);
}
protected function getProductDownloads(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_download');
$downloads = $queryBuilder
->select('d.*')
->from('tx_vitec_domain_model_download', 'd')
->join(
'd',
'tx_vitec_product_download_mm',
'mm',
'mm.uid_foreign = d.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('d.deleted', 0),
$queryBuilder->expr()->eq('d.hidden', 0),
$queryBuilder->expr()->eq('d.hideonwebsite', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($downloads as $download) {
$fileInfo = null;
if (!empty($download['file'])) {
$fileQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRef = $fileQueryBuilder
->select('fr.uid', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter((int)$download['uid'], ParameterType::INTEGER)),
$fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.deleted', 0),
$fileQueryBuilder->expr()->eq('f.missing', 0)
)
->orderBy('fr.sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if ($fileRef) {
$fileInfo = [
'uid' => (int)$fileRef['file_uid'],
'name' => $fileRef['name'],
'url' => '/fileadmin' . $fileRef['identifier'],
'size' => (int)$fileRef['size'],
'extension' => $fileRef['extension'],
'mimeType' => $fileRef['mime_type'] ?? '',
];
}
}
$result[] = [
'uid' => (int)$download['uid'],
'title' => $download['title'] ?? '',
'slug' => $download['slug'] ?? '',
'teaser' => $download['teaser'] ?? '',
'description' => $download['description'] ?? '',
'keywords' => $download['keywords'] ?? '',
'icon' => $download['icon'] ?? '',
'file' => $fileInfo,
];
}
return $result;
}
protected function getRelatedProducts(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$related = $queryBuilder
->select('p.uid', 'p.title', 'p.slug', 'p.subtitle', 'p.teaser', 'p.description')
->from('tx_vitec_domain_model_product', 'p')
->join(
'p',
'tx_vitec_product_related_mm',
'mm',
'mm.uid_foreign = p.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('p.deleted', 0),
$queryBuilder->expr()->eq('p.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($related as $rel) {
$relUid = (int)$rel['uid'];
$result[] = [
'uid' => $relUid,
'title' => (string)($rel['title'] ?? ''),
'slug' => (string)($rel['slug'] ?? ''),
'subtitle' => (string)($rel['subtitle'] ?? ''),
'teaser' => (string)($rel['teaser'] ?? ''),
'description' => (string)($rel['description'] ?? ''),
'link' => '/product/' . (string)($rel['slug'] ?? ''),
'images' => $this->getProductImages($relUid),
];
}
return $result;
}
}

View File

View File

@@ -0,0 +1,470 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Imaging\ImageService;
use Doctrine\DBAL\ParameterType;
/**
* UserFunc to render a single product as JSON for headless output.
*
* `render()` performs page discovery (top-level plugin via TypoScript).
* `renderForRecord()` processes one specific tt_content row and is reused
* by ContainerChildrenProcessor for product-show plugins nested in a
* b13 container.
*/
class ProductShowJsonRenderer
{
public function render(string $content, array $conf): string
{
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$contentElements = $queryBuilder
->select('*')
->from('tt_content')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter('vitec_productshow', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if (empty($contentElements)) {
return '';
}
return $this->renderForRecord($contentElements[0]);
}
/**
* Render exactly the given tt_content row (the product-show plugin element).
* Exception-safe: returns '' on any failure.
*
* @param array<string,mixed> $contentElement
*/
public function renderForRecord(array $contentElement): string
{
try {
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$productUid = (int)($settings['product'] ?? 0);
$layout = (int)($settings['layout'] ?? 0);
$debugMode = (bool)($settings['debug'] ?? false);
if (!$productUid) {
$routeParams = $GLOBALS['TYPO3_REQUEST']->getQueryParams();
$productParam = $routeParams['tx_vitec_productshow']['product'] ?? null;
if ($productParam) {
if (!is_numeric($productParam)) {
$slugQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$productBySlug = $slugQueryBuilder
->select('uid')
->from('tx_vitec_domain_model_product')
->where(
$slugQueryBuilder->expr()->eq('slug', $slugQueryBuilder->createNamedParameter($productParam)),
$slugQueryBuilder->expr()->eq('deleted', 0),
$slugQueryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
$productUid = (int)($productBySlug['uid'] ?? 0);
} else {
$productUid = (int)$productParam;
}
}
}
if (!$productUid) {
return $debugMode
? json_encode(['error' => 'No product selected or found', 'debug' => ['settings' => $settings]])
: '';
}
$productQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$product = $productQueryBuilder
->select('*')
->from('tx_vitec_domain_model_product')
->where(
$productQueryBuilder->expr()->eq('uid', $productQueryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$productQueryBuilder->expr()->eq('deleted', 0),
$productQueryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
if (!$product) {
return $debugMode
? json_encode(['error' => 'Product not found', 'debug' => ['productUid' => $productUid]])
: '';
}
$response = [
'product' => $this->serializeProduct($product),
'layout' => $layout,
'settings' => [
'layout' => $layout,
],
];
if ($debugMode) {
$response['debug'] = [
'pageId' => $pageId,
'productUid' => $productUid,
'layout' => $layout,
'settings' => $settings,
];
}
return json_encode($response);
} catch (\Throwable $e) {
return '';
}
}
/**
* @param array<string,mixed> $product
* @return array<string,mixed>
*/
protected function serializeProduct(array $product): array
{
$uid = (int)$product['uid'];
return [
'uid' => $uid,
'title' => (string)($product['title'] ?? ''),
'slug' => (string)($product['slug'] ?? ''),
'urltitle' => (string)($product['urltitle'] ?? ''),
'seotitle' => (string)($product['seotitle'] ?? ''),
'seometa' => (string)($product['seometa'] ?? ''),
'keywords' => (string)($product['keywords'] ?? ''),
'structureddata' => (string)($product['structureddata'] ?? ''),
'teaser' => (string)($product['teaser'] ?? ''),
'subtitle' => (string)($product['subtitle'] ?? ''),
'video' => (string)($product['video'] ?? ''),
'applications' => (string)($product['applications'] ?? ''),
'description' => (string)($product['description'] ?? ''),
'highlights' => (string)($product['highlights'] ?? ''),
'shortcutpid' => (string)($product['shortcutpid'] ?? ''),
'contentelement' => (string)($product['contentelement'] ?? ''),
'contentelementcta' => (string)($product['contentelementcta'] ?? ''),
'hideonapp' => (bool)($product['hideonapp'] ?? false),
'hideonwebsite' => (bool)($product['hideonwebsite'] ?? false),
'hideondatasheets' => (bool)($product['hideondatasheets'] ?? false),
'hideonproducts' => (bool)($product['hideonproducts'] ?? false),
'shortcut' => (bool)($product['shortcut'] ?? false),
'legacy' => (bool)($product['legacy'] ?? false),
'supportproduct' => (bool)($product['supportproduct'] ?? false),
'subproduct' => (bool)($product['subproduct'] ?? false),
'link' => '/product/' . (string)($product['slug'] ?? ''),
'categories' => $this->getProductCategories($uid),
'images' => $this->getProductImages($uid),
'downloads' => $this->getProductDownloads($uid),
'ogimage' => $this->getProductOgImage($uid),
'relatedprodukt' => $this->getRelatedProducts($uid),
];
}
protected function getProductImages(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileReferences = $queryBuilder
->select('*')
->from('sys_file_reference')
->where(
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('productimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->orderBy('sorting_foreign', 'ASC')
->executeQuery()
->fetchAllAssociative();
if (empty($fileReferences)) {
return [];
}
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$images = [];
foreach ($fileReferences as $fileRef) {
try {
$fileReference = $resourceFactory->getFileReferenceObject($fileRef['uid']);
$originalFile = $fileReference->getOriginalFile();
$processedImage = $imageService->applyProcessingInstructions(
$fileReference,
['width' => '1874c', 'height' => '625c']
);
$srcset = [];
foreach ([400, 800, 1200, 1600] as $width) {
$processedVariant = $imageService->applyProcessingInstructions(
$fileReference,
['width' => $width . 'c', 'height' => (int)($width / 3) . 'c']
);
$srcset[] = [
'url' => $imageService->getImageUri($processedVariant),
'width' => $width,
'descriptor' => $width . 'w',
];
}
$images[] = [
'uid' => $fileRef['uid'],
'url' => $imageService->getImageUri($processedImage),
'title' => $fileReference->getTitle() ?: '',
'alternative' => $fileReference->getAlternative() ?: '',
'description' => $fileReference->getDescription() ?: '',
'srcset' => $srcset,
'properties' => [
'width' => $originalFile->getProperty('width'),
'height' => $originalFile->getProperty('height'),
'mimeType' => $originalFile->getMimeType(),
],
];
} catch (\Exception $e) {
continue;
}
}
return $images;
}
/**
* @return array<string,mixed>|null
*/
protected function getProductOgImage(int $productUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRef = $queryBuilder
->select('*')
->from('sys_file_reference')
->where(
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('ogimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->orderBy('sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$fileRef) {
return null;
}
try {
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject($fileRef['uid']);
$originalFile = $fileReference->getOriginalFile();
$processedImage = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 1200]
);
return [
'uid' => $fileRef['uid'],
'url' => $imageService->getImageUri($processedImage),
'title' => $fileReference->getTitle() ?: '',
'alternative' => $fileReference->getAlternative() ?: '',
'description' => $fileReference->getDescription() ?: '',
'properties' => [
'width' => $originalFile->getProperty('width'),
'height' => $originalFile->getProperty('height'),
'mimeType' => $originalFile->getMimeType(),
],
];
} catch (\Exception $e) {
return null;
}
}
protected function getProductCategories(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $queryBuilder
->select('c.uid', 'c.title', 'c.description')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) .
' AND mm.fieldname = ' .
$queryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('c.deleted', 0),
$queryBuilder->expr()->eq('c.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => $cat['title'],
'description' => $cat['description'] ?? '',
];
}, $categories);
}
protected function getProductDownloads(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_download');
$downloads = $queryBuilder
->select('d.*')
->from('tx_vitec_domain_model_download', 'd')
->join(
'd',
'tx_vitec_product_download_mm',
'mm',
'mm.uid_foreign = d.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('d.deleted', 0),
$queryBuilder->expr()->eq('d.hidden', 0),
$queryBuilder->expr()->eq('d.hideonwebsite', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($downloads as $download) {
$fileInfo = null;
if (!empty($download['file'])) {
$fileQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRef = $fileQueryBuilder
->select('fr.uid', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter((int)$download['uid'], ParameterType::INTEGER)),
$fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.deleted', 0),
$fileQueryBuilder->expr()->eq('f.missing', 0)
)
->orderBy('fr.sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if ($fileRef) {
$fileInfo = [
'uid' => (int)$fileRef['file_uid'],
'name' => $fileRef['name'],
'url' => '/fileadmin' . $fileRef['identifier'],
'size' => (int)$fileRef['size'],
'extension' => $fileRef['extension'],
'mimeType' => $fileRef['mime_type'] ?? '',
];
}
}
$result[] = [
'uid' => (int)$download['uid'],
'title' => $download['title'] ?? '',
'slug' => $download['slug'] ?? '',
'teaser' => $download['teaser'] ?? '',
'description' => $download['description'] ?? '',
'keywords' => $download['keywords'] ?? '',
'icon' => $download['icon'] ?? '',
'file' => $fileInfo,
];
}
return $result;
}
protected function getRelatedProducts(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$related = $queryBuilder
->select('p.uid', 'p.title', 'p.slug', 'p.subtitle', 'p.teaser', 'p.description')
->from('tx_vitec_domain_model_product', 'p')
->join(
'p',
'tx_vitec_product_related_mm',
'mm',
'mm.uid_foreign = p.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('p.deleted', 0),
$queryBuilder->expr()->eq('p.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($related as $rel) {
$relUid = (int)$rel['uid'];
$result[] = [
'uid' => $relUid,
'title' => (string)($rel['title'] ?? ''),
'slug' => (string)($rel['slug'] ?? ''),
'subtitle' => (string)($rel['subtitle'] ?? ''),
'teaser' => (string)($rel['teaser'] ?? ''),
'description' => (string)($rel['description'] ?? ''),
'link' => '/product/' . (string)($rel['slug'] ?? ''),
'images' => $this->getProductImages($relUid),
];
}
return $result;
}
}

View File

@@ -0,0 +1,514 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Imaging\ImageService;
use Doctrine\DBAL\ParameterType;
/**
* UserFunc to render a single product as JSON for headless output.
*
* `render()` performs page discovery (top-level plugin via TypoScript).
* `renderForRecord()` processes one specific tt_content row and is reused
* by ContainerChildrenProcessor for product-show plugins nested in a
* b13 container.
*/
class ProductShowJsonRenderer
{
public function render(string $content, array $conf): string
{
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$contentElements = $queryBuilder
->select('*')
->from('tt_content')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter('vitec_productshow', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if (empty($contentElements)) {
return '';
}
return $this->renderForRecord($contentElements[0]);
}
/**
* Render exactly the given tt_content row (the product-show plugin element).
* Exception-safe: returns '' on any failure.
*
* @param array<string,mixed> $contentElement
*/
public function renderForRecord(array $contentElement): string
{
try {
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$productUid = (int)($settings['product'] ?? 0);
$layout = (int)($settings['layout'] ?? 0);
$debugMode = (bool)($settings['debug'] ?? false);
if (!$productUid) {
$routeParams = $GLOBALS['TYPO3_REQUEST']->getQueryParams();
$productParam = $routeParams['tx_vitec_productshow']['product'] ?? null;
if ($productParam) {
if (!is_numeric($productParam)) {
$slugQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$productBySlug = $slugQueryBuilder
->select('uid')
->from('tx_vitec_domain_model_product')
->where(
$slugQueryBuilder->expr()->eq('slug', $slugQueryBuilder->createNamedParameter($productParam)),
$slugQueryBuilder->expr()->eq('deleted', 0),
$slugQueryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
$productUid = (int)($productBySlug['uid'] ?? 0);
} else {
$productUid = (int)$productParam;
}
}
}
if (!$productUid) {
return $debugMode
? json_encode(['error' => 'No product selected or found', 'debug' => ['settings' => $settings]])
: '';
}
$productQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$product = $productQueryBuilder
->select('*')
->from('tx_vitec_domain_model_product')
->where(
$productQueryBuilder->expr()->eq('uid', $productQueryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$productQueryBuilder->expr()->eq('deleted', 0),
$productQueryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
if (!$product) {
return $debugMode
? json_encode(['error' => 'Product not found', 'debug' => ['productUid' => $productUid]])
: '';
}
$response = [
'product' => $this->serializeProduct($product),
'layout' => $layout,
'settings' => [
'layout' => $layout,
],
];
if ($debugMode) {
$response['debug'] = [
'pageId' => $pageId,
'productUid' => $productUid,
'layout' => $layout,
'settings' => $settings,
];
}
return json_encode($response);
} catch (\Throwable $e) {
return '';
}
}
/**
* @param array<string,mixed> $product
* @return array<string,mixed>
*/
protected function serializeProduct(array $product): array
{
$uid = (int)$product['uid'];
return [
'uid' => $uid,
'title' => (string)($product['title'] ?? ''),
'slug' => (string)($product['slug'] ?? ''),
'urltitle' => (string)($product['urltitle'] ?? ''),
'seotitle' => (string)($product['seotitle'] ?? ''),
'seometa' => (string)($product['seometa'] ?? ''),
'keywords' => (string)($product['keywords'] ?? ''),
'structureddata' => (string)($product['structureddata'] ?? ''),
'teaser' => (string)($product['teaser'] ?? ''),
'subtitle' => (string)($product['subtitle'] ?? ''),
'video' => (string)($product['video'] ?? ''),
'applications' => (string)($product['applications'] ?? ''),
'description' => (string)($product['description'] ?? ''),
'highlights' => (string)($product['highlights'] ?? ''),
'shortcutpid' => (string)($product['shortcutpid'] ?? ''),
'contentelement' => (string)($product['contentelement'] ?? ''),
'contentelementcta' => (string)($product['contentelementcta'] ?? ''),
'hideonapp' => (bool)($product['hideonapp'] ?? false),
'hideonwebsite' => (bool)($product['hideonwebsite'] ?? false),
'hideondatasheets' => (bool)($product['hideondatasheets'] ?? false),
'hideonproducts' => (bool)($product['hideonproducts'] ?? false),
'shortcut' => (bool)($product['shortcut'] ?? false),
'legacy' => (bool)($product['legacy'] ?? false),
'supportproduct' => (bool)($product['supportproduct'] ?? false),
'subproduct' => (bool)($product['subproduct'] ?? false),
'link' => '/product/' . (string)($product['slug'] ?? ''),
'categories' => $this->getProductCategories($uid),
'images' => $this->getProductImages($uid),
'downloads' => $this->getProductDownloads($uid),
'ogimage' => $this->getProductOgImage($uid),
'videofile' => $this->getProductVideoFile($uid),
'relatedprodukt' => $this->getRelatedProducts($uid),
];
}
protected function getProductImages(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileReferences = $queryBuilder
->select('*')
->from('sys_file_reference')
->where(
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('productimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->orderBy('sorting_foreign', 'ASC')
->executeQuery()
->fetchAllAssociative();
if (empty($fileReferences)) {
return [];
}
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$images = [];
foreach ($fileReferences as $fileRef) {
try {
$fileReference = $resourceFactory->getFileReferenceObject($fileRef['uid']);
$originalFile = $fileReference->getOriginalFile();
$processedImage = $imageService->applyProcessingInstructions(
$fileReference,
['width' => '1874c', 'height' => '625c']
);
$srcset = [];
foreach ([400, 800, 1200, 1600] as $width) {
$processedVariant = $imageService->applyProcessingInstructions(
$fileReference,
['width' => $width . 'c', 'height' => (int)($width / 3) . 'c']
);
$srcset[] = [
'url' => $imageService->getImageUri($processedVariant),
'width' => $width,
'descriptor' => $width . 'w',
];
}
$images[] = [
'uid' => $fileRef['uid'],
'url' => $imageService->getImageUri($processedImage),
'title' => $fileReference->getTitle() ?: '',
'alternative' => $fileReference->getAlternative() ?: '',
'description' => $fileReference->getDescription() ?: '',
'srcset' => $srcset,
'properties' => [
'width' => $originalFile->getProperty('width'),
'height' => $originalFile->getProperty('height'),
'mimeType' => $originalFile->getMimeType(),
],
];
} catch (\Exception $e) {
continue;
}
}
return $images;
}
/**
* @return array<string,mixed>|null
*/
protected function getProductOgImage(int $productUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRef = $queryBuilder
->select('*')
->from('sys_file_reference')
->where(
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('ogimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->orderBy('sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$fileRef) {
return null;
}
try {
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject($fileRef['uid']);
$originalFile = $fileReference->getOriginalFile();
$processedImage = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 1200]
);
return [
'uid' => $fileRef['uid'],
'url' => $imageService->getImageUri($processedImage),
'title' => $fileReference->getTitle() ?: '',
'alternative' => $fileReference->getAlternative() ?: '',
'description' => $fileReference->getDescription() ?: '',
'properties' => [
'width' => $originalFile->getProperty('width'),
'height' => $originalFile->getProperty('height'),
'mimeType' => $originalFile->getMimeType(),
],
];
} catch (\Exception $e) {
return null;
}
}
/**
* Resolve the uploaded video file (single FAL reference, fieldname=videofile).
*
* @return array<string,mixed>|null
*/
protected function getProductVideoFile(int $productUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$row = $queryBuilder
->select('fr.uid', 'fr.title', 'fr.description', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$queryBuilder->expr()->eq('fr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', \Doctrine\DBAL\ParameterType::STRING)),
$queryBuilder->expr()->eq('fr.fieldname', $queryBuilder->createNamedParameter('videofile', \Doctrine\DBAL\ParameterType::STRING)),
$queryBuilder->expr()->eq('fr.uid_foreign', $queryBuilder->createNamedParameter($productUid, \Doctrine\DBAL\ParameterType::INTEGER)),
$queryBuilder->expr()->eq('fr.deleted', 0),
$queryBuilder->expr()->eq('fr.hidden', 0),
$queryBuilder->expr()->eq('f.missing', 0)
)
->orderBy('fr.sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$row) {
return null;
}
return [
'uid' => (int)$row['file_uid'],
'name' => (string)($row['name'] ?? ''),
'url' => '/fileadmin' . ($row['identifier'] ?? ''),
'size' => (int)($row['size'] ?? 0),
'extension' => (string)($row['extension'] ?? ''),
'mimeType' => (string)($row['mime_type'] ?? ''),
'title' => (string)($row['title'] ?? ''),
'description' => (string)($row['description'] ?? ''),
];
}
protected function getProductCategories(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $queryBuilder
->select('c.uid', 'c.title', 'c.description')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) .
' AND mm.fieldname = ' .
$queryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('c.deleted', 0),
$queryBuilder->expr()->eq('c.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => $cat['title'],
'description' => $cat['description'] ?? '',
];
}, $categories);
}
protected function getProductDownloads(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_download');
$downloads = $queryBuilder
->select('d.*')
->from('tx_vitec_domain_model_download', 'd')
->join(
'd',
'tx_vitec_product_download_mm',
'mm',
'mm.uid_foreign = d.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('d.deleted', 0),
$queryBuilder->expr()->eq('d.hidden', 0),
$queryBuilder->expr()->eq('d.hideonwebsite', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($downloads as $download) {
$fileInfo = null;
if (!empty($download['file'])) {
$fileQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRef = $fileQueryBuilder
->select('fr.uid', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter((int)$download['uid'], ParameterType::INTEGER)),
$fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.deleted', 0),
$fileQueryBuilder->expr()->eq('f.missing', 0)
)
->orderBy('fr.sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if ($fileRef) {
$fileInfo = [
'uid' => (int)$fileRef['file_uid'],
'name' => $fileRef['name'],
'url' => '/fileadmin' . $fileRef['identifier'],
'size' => (int)$fileRef['size'],
'extension' => $fileRef['extension'],
'mimeType' => $fileRef['mime_type'] ?? '',
];
}
}
$result[] = [
'uid' => (int)$download['uid'],
'title' => $download['title'] ?? '',
'slug' => $download['slug'] ?? '',
'teaser' => $download['teaser'] ?? '',
'description' => $download['description'] ?? '',
'keywords' => $download['keywords'] ?? '',
'icon' => $download['icon'] ?? '',
'file' => $fileInfo,
];
}
return $result;
}
protected function getRelatedProducts(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$related = $queryBuilder
->select('p.uid', 'p.title', 'p.slug', 'p.subtitle', 'p.teaser', 'p.description')
->from('tx_vitec_domain_model_product', 'p')
->join(
'p',
'tx_vitec_product_related_mm',
'mm',
'mm.uid_foreign = p.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('p.deleted', 0),
$queryBuilder->expr()->eq('p.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($related as $rel) {
$relUid = (int)$rel['uid'];
$result[] = [
'uid' => $relUid,
'title' => (string)($rel['title'] ?? ''),
'slug' => (string)($rel['slug'] ?? ''),
'subtitle' => (string)($rel['subtitle'] ?? ''),
'teaser' => (string)($rel['teaser'] ?? ''),
'description' => (string)($rel['description'] ?? ''),
'link' => '/product/' . (string)($rel['slug'] ?? ''),
'images' => $this->getProductImages($relUid),
];
}
return $result;
}
}

View File

@@ -0,0 +1,514 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Imaging\ImageService;
use Doctrine\DBAL\ParameterType;
/**
* UserFunc to render a single product as JSON for headless output.
*
* `render()` performs page discovery (top-level plugin via TypoScript).
* `renderForRecord()` processes one specific tt_content row and is reused
* by ContainerChildrenProcessor for product-show plugins nested in a
* b13 container.
*/
class ProductShowJsonRenderer
{
public function render(string $content, array $conf): string
{
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$contentElements = $queryBuilder
->select('*')
->from('tt_content')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter('vitec_productshow', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if (empty($contentElements)) {
return '';
}
return $this->renderForRecord($contentElements[0]);
}
/**
* Render exactly the given tt_content row (the product-show plugin element).
* Exception-safe: returns '' on any failure.
*
* @param array<string,mixed> $contentElement
*/
public function renderForRecord(array $contentElement): string
{
try {
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$productUid = (int)($settings['product'] ?? 0);
$layout = (int)($settings['layout'] ?? 0);
$debugMode = (bool)($settings['debug'] ?? false);
if (!$productUid) {
$routeParams = $GLOBALS['TYPO3_REQUEST']->getQueryParams();
$productParam = $routeParams['tx_vitec_productshow']['product'] ?? null;
if ($productParam) {
if (!is_numeric($productParam)) {
$slugQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$productBySlug = $slugQueryBuilder
->select('uid')
->from('tx_vitec_domain_model_product')
->where(
$slugQueryBuilder->expr()->eq('slug', $slugQueryBuilder->createNamedParameter($productParam)),
$slugQueryBuilder->expr()->eq('deleted', 0),
$slugQueryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
$productUid = (int)($productBySlug['uid'] ?? 0);
} else {
$productUid = (int)$productParam;
}
}
}
if (!$productUid) {
return $debugMode
? json_encode(['error' => 'No product selected or found', 'debug' => ['settings' => $settings]])
: '';
}
$productQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$product = $productQueryBuilder
->select('*')
->from('tx_vitec_domain_model_product')
->where(
$productQueryBuilder->expr()->eq('uid', $productQueryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$productQueryBuilder->expr()->eq('deleted', 0),
$productQueryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
if (!$product) {
return $debugMode
? json_encode(['error' => 'Product not found', 'debug' => ['productUid' => $productUid]])
: '';
}
$response = [
'product' => $this->serializeProduct($product),
'layout' => $layout,
'settings' => [
'layout' => $layout,
],
];
if ($debugMode) {
$response['debug'] = [
'pageId' => $pageId,
'productUid' => $productUid,
'layout' => $layout,
'settings' => $settings,
];
}
return json_encode($response);
} catch (\Throwable $e) {
return '';
}
}
/**
* @param array<string,mixed> $product
* @return array<string,mixed>
*/
protected function serializeProduct(array $product): array
{
$uid = (int)$product['uid'];
return [
'uid' => $uid,
'title' => (string)($product['title'] ?? ''),
'slug' => (string)($product['slug'] ?? ''),
'urltitle' => (string)($product['urltitle'] ?? ''),
'seotitle' => (string)($product['seotitle'] ?? ''),
'seometa' => (string)($product['seometa'] ?? ''),
'keywords' => (string)($product['keywords'] ?? ''),
'structureddata' => (string)($product['structureddata'] ?? ''),
'teaser' => (string)($product['teaser'] ?? ''),
'subtitle' => (string)($product['subtitle'] ?? ''),
'video' => (string)($product['video'] ?? ''),
'applications' => (string)($product['applications'] ?? ''),
'description' => (string)($product['description'] ?? ''),
'highlights' => (string)($product['highlights'] ?? ''),
'shortcutpid' => (string)($product['shortcutpid'] ?? ''),
'contentelement' => \Evomedien\Vitec\Service\ContentElementResolver::resolveLink((string)($product['contentelement'] ?? '')),
'contentelementcta' => \Evomedien\Vitec\Service\ContentElementResolver::resolveLink((string)($product['contentelementcta'] ?? '')),
'hideonapp' => (bool)($product['hideonapp'] ?? false),
'hideonwebsite' => (bool)($product['hideonwebsite'] ?? false),
'hideondatasheets' => (bool)($product['hideondatasheets'] ?? false),
'hideonproducts' => (bool)($product['hideonproducts'] ?? false),
'shortcut' => (bool)($product['shortcut'] ?? false),
'legacy' => (bool)($product['legacy'] ?? false),
'supportproduct' => (bool)($product['supportproduct'] ?? false),
'subproduct' => (bool)($product['subproduct'] ?? false),
'link' => '/product/' . (string)($product['slug'] ?? ''),
'categories' => $this->getProductCategories($uid),
'images' => $this->getProductImages($uid),
'downloads' => $this->getProductDownloads($uid),
'ogimage' => $this->getProductOgImage($uid),
'videofile' => $this->getProductVideoFile($uid),
'relatedprodukt' => $this->getRelatedProducts($uid),
];
}
protected function getProductImages(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileReferences = $queryBuilder
->select('*')
->from('sys_file_reference')
->where(
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('productimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->orderBy('sorting_foreign', 'ASC')
->executeQuery()
->fetchAllAssociative();
if (empty($fileReferences)) {
return [];
}
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$images = [];
foreach ($fileReferences as $fileRef) {
try {
$fileReference = $resourceFactory->getFileReferenceObject($fileRef['uid']);
$originalFile = $fileReference->getOriginalFile();
$processedImage = $imageService->applyProcessingInstructions(
$fileReference,
['width' => '1874c', 'height' => '625c']
);
$srcset = [];
foreach ([400, 800, 1200, 1600] as $width) {
$processedVariant = $imageService->applyProcessingInstructions(
$fileReference,
['width' => $width . 'c', 'height' => (int)($width / 3) . 'c']
);
$srcset[] = [
'url' => $imageService->getImageUri($processedVariant),
'width' => $width,
'descriptor' => $width . 'w',
];
}
$images[] = [
'uid' => $fileRef['uid'],
'url' => $imageService->getImageUri($processedImage),
'title' => $fileReference->getTitle() ?: '',
'alternative' => $fileReference->getAlternative() ?: '',
'description' => $fileReference->getDescription() ?: '',
'srcset' => $srcset,
'properties' => [
'width' => $originalFile->getProperty('width'),
'height' => $originalFile->getProperty('height'),
'mimeType' => $originalFile->getMimeType(),
],
];
} catch (\Exception $e) {
continue;
}
}
return $images;
}
/**
* @return array<string,mixed>|null
*/
protected function getProductOgImage(int $productUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRef = $queryBuilder
->select('*')
->from('sys_file_reference')
->where(
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)),
$queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('ogimage', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->orderBy('sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$fileRef) {
return null;
}
try {
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject($fileRef['uid']);
$originalFile = $fileReference->getOriginalFile();
$processedImage = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 1200]
);
return [
'uid' => $fileRef['uid'],
'url' => $imageService->getImageUri($processedImage),
'title' => $fileReference->getTitle() ?: '',
'alternative' => $fileReference->getAlternative() ?: '',
'description' => $fileReference->getDescription() ?: '',
'properties' => [
'width' => $originalFile->getProperty('width'),
'height' => $originalFile->getProperty('height'),
'mimeType' => $originalFile->getMimeType(),
],
];
} catch (\Exception $e) {
return null;
}
}
/**
* Resolve the uploaded video file (single FAL reference, fieldname=videofile).
*
* @return array<string,mixed>|null
*/
protected function getProductVideoFile(int $productUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$row = $queryBuilder
->select('fr.uid', 'fr.title', 'fr.description', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$queryBuilder->expr()->eq('fr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', \Doctrine\DBAL\ParameterType::STRING)),
$queryBuilder->expr()->eq('fr.fieldname', $queryBuilder->createNamedParameter('videofile', \Doctrine\DBAL\ParameterType::STRING)),
$queryBuilder->expr()->eq('fr.uid_foreign', $queryBuilder->createNamedParameter($productUid, \Doctrine\DBAL\ParameterType::INTEGER)),
$queryBuilder->expr()->eq('fr.deleted', 0),
$queryBuilder->expr()->eq('fr.hidden', 0),
$queryBuilder->expr()->eq('f.missing', 0)
)
->orderBy('fr.sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$row) {
return null;
}
return [
'uid' => (int)$row['file_uid'],
'name' => (string)($row['name'] ?? ''),
'url' => '/fileadmin' . ($row['identifier'] ?? ''),
'size' => (int)($row['size'] ?? 0),
'extension' => (string)($row['extension'] ?? ''),
'mimeType' => (string)($row['mime_type'] ?? ''),
'title' => (string)($row['title'] ?? ''),
'description' => (string)($row['description'] ?? ''),
];
}
protected function getProductCategories(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $queryBuilder
->select('c.uid', 'c.title', 'c.description')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) .
' AND mm.fieldname = ' .
$queryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('c.deleted', 0),
$queryBuilder->expr()->eq('c.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => $cat['title'],
'description' => $cat['description'] ?? '',
];
}, $categories);
}
protected function getProductDownloads(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_download');
$downloads = $queryBuilder
->select('d.*')
->from('tx_vitec_domain_model_download', 'd')
->join(
'd',
'tx_vitec_product_download_mm',
'mm',
'mm.uid_foreign = d.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('d.deleted', 0),
$queryBuilder->expr()->eq('d.hidden', 0),
$queryBuilder->expr()->eq('d.hideonwebsite', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($downloads as $download) {
$fileInfo = null;
if (!empty($download['file'])) {
$fileQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRef = $fileQueryBuilder
->select('fr.uid', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter((int)$download['uid'], ParameterType::INTEGER)),
$fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.deleted', 0),
$fileQueryBuilder->expr()->eq('f.missing', 0)
)
->orderBy('fr.sorting_foreign', 'ASC')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if ($fileRef) {
$fileInfo = [
'uid' => (int)$fileRef['file_uid'],
'name' => $fileRef['name'],
'url' => '/fileadmin' . $fileRef['identifier'],
'size' => (int)$fileRef['size'],
'extension' => $fileRef['extension'],
'mimeType' => $fileRef['mime_type'] ?? '',
];
}
}
$result[] = [
'uid' => (int)$download['uid'],
'title' => $download['title'] ?? '',
'slug' => $download['slug'] ?? '',
'teaser' => $download['teaser'] ?? '',
'description' => $download['description'] ?? '',
'keywords' => $download['keywords'] ?? '',
'icon' => $download['icon'] ?? '',
'file' => $fileInfo,
];
}
return $result;
}
protected function getRelatedProducts(int $productUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_product');
$related = $queryBuilder
->select('p.uid', 'p.title', 'p.slug', 'p.subtitle', 'p.teaser', 'p.description')
->from('tx_vitec_domain_model_product', 'p')
->join(
'p',
'tx_vitec_product_related_mm',
'mm',
'mm.uid_foreign = p.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('p.deleted', 0),
$queryBuilder->expr()->eq('p.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$result = [];
foreach ($related as $rel) {
$relUid = (int)$rel['uid'];
$result[] = [
'uid' => $relUid,
'title' => (string)($rel['title'] ?? ''),
'slug' => (string)($rel['slug'] ?? ''),
'subtitle' => (string)($rel['subtitle'] ?? ''),
'teaser' => (string)($rel['teaser'] ?? ''),
'description' => (string)($rel['description'] ?? ''),
'link' => '/product/' . (string)($rel['slug'] ?? ''),
'images' => $this->getProductImages($relUid),
];
}
return $result;
}
}

View File

View File

View File

0
packages/vitec/Classes/View/VitecBackendLayoutView.php Normal file → Executable file
View File

0
packages/vitec/Configuration/FlexForms/Container.xml Normal file → Executable file
View File

0
packages/vitec/Configuration/Icons.php Normal file → Executable file
View File

0
packages/vitec/Configuration/TCA/Overrides/pages.php Normal file → Executable file
View File

View File

View File

View File

@@ -0,0 +1,541 @@
<?php
return [
'ctrl' => [
'title' => 'VITEC Product',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'versioningWS' => true,
'languageField' => 'sys_language_uid',
'transOrigPointerField' => 'l10n_parent',
'transOrigDiffSourceField' => 'l10n_diffsource',
'delete' => 'deleted',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'title,slug',
'iconfile' => 'EXT:vitec/Resources/Public/Icons/tx_vitec_domain_model_product.gif',
'security' => [
'ignorePageTypeRestriction' => true,
],
],
'types' => [
'1' => ['showitem' => 'title, subtitle, slug, teaser, description, applications, highlights, contentelement, contentelementcta, downloads,
--div--;SEO, seotitle, urltitle, seometa, keywords, structureddata,
--div--;Images and Videos, productimage, ogimage, video,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories, categories,
--div--;Visibility, hideonapp, hideonwebsite, hideondatasheets, hideonproducts, shortcut, shortcutpid,
--div--;Misc, legacy, supportproduct, subproduct, relatedprodukt,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime'],
],
'columns' => [
'sys_language_uid' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
'config' => [
'type' => 'language',
],
],
'l10n_parent' => [
'displayCond' => 'FIELD:sys_language_uid:>:0',
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'default' => 0,
'items' => [
['', 0],
],
'foreign_table' => 'tx_vitec_domain_model_product',
'foreign_table_where' => 'AND {#tx_vitec_domain_model_product}.{#pid}=###CURRENT_PID### AND {#tx_vitec_domain_model_product}.{#sys_language_uid} IN (-1,0)',
],
],
'l10n_diffsource' => [
'config' => [
'type' => 'passthrough',
],
],
'hidden' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => true
]
],
],
],
'starttime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
'config' => [
'type' => 'input',
'renderType' => 'inputDateTime',
'eval' => 'datetime,int',
'default' => 0,
'behaviour' => [
'allowLanguageSynchronization' => true
]
],
],
'endtime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
'config' => [
'type' => 'input',
'renderType' => 'inputDateTime',
'eval' => 'datetime,int',
'default' => 0,
'range' => [
'upper' => mktime(0, 0, 0, 1, 1, 2038)
],
'behaviour' => [
'allowLanguageSynchronization' => true
]
],
],
'categories' => [
'config' => [
'type' => 'category'
]
],
'title' => [
'exclude' => false,
'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.title',
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.title.description',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'required' => true,
'default' => ''
],
],
'slug' => [
'exclude' => false,
'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.slug',
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.slug.description',
'config' => [
'type' => 'slug',
'size' => 50,
'generatorOptions' => [
'fields' => ['title'], // TODO: adjust this field to the one you want to use
'fieldSeparator' => '-',
'replacements' => [
'/' => '',
],
],
'fallbackCharacter' => '-',
'eval' => 'uniqueInPid',
],
],
'seotitle' => [
'exclude' => false,
'label' => 'SEO-Title',
'description' => 'SEO specific title, used for sharing options',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'urltitle' => [
'exclude' => false,
'label' => 'URL-Title',
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.urltitle.description',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'video' => [
'exclude' => false,
'label' => 'Product Video - just the Youtube Clip ID',
'description' => 'Product Video - just the Youtube Clip ID',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'downloads' => [
'exclude' => true,
'label' => 'Downloads',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_vitec_domain_model_download',
'MM' => 'tx_vitec_product_download_mm', // Define the MM table for the relation
'size' => 10,
'maxitems' => 9999,
'minitems' => 0,
'enableMultiSelectFilterTextfield' => true, // Optional: Adds a search box for filtering
],
],
'structureddata' => [
'exclude' => true,
'label' => 'Structured Data in JSON Format',
'config' => [
'type' => 'text',
'cols' => 40,
'rows' => 15,
'eval' => 'trim'
]
],
'keywords' => [
'exclude' => true,
'label' => 'Keywords',
'description' => 'Keywords for SEO, separated by commas',
'config' => [
'type' => 'text',
'cols' => 40,
'rows' => 15,
'eval' => 'trim'
]
],
'seometa' => [
'exclude' => true,
'label' => 'SEO Meta Description',
'description' => 'SEO specific meta description, used for sharing options',
'config' => [
'type' => 'text',
'cols' => 40,
'rows' => 15,
'eval' => 'trim'
]
],
'teaser' => [
'exclude' => true,
'label' => 'Short Teaser (optional)',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'subtitle' => [
'exclude' => true,
'label' => 'Subtitle',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'hideonapp' => [
'exclude' => true,
'label' => 'If checked - Product will not be shown on App',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'hideonwebsite' => [
'exclude' => true,
'label' => 'If checked - Product will not be shown on Website',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'hideonproducts' => [
'exclude' => true,
'label' => 'If checked - Product will not be shown on Product Section',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'hideondatasheets' => [
'exclude' => true,
'label' => 'If checked - Product will not be shown on Datasheets Page',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'applications' => [
'exclude' => true,
'label' => 'Applications',
'config' => [
'type' => 'text',
'enableRichtext' => 'true',
'eval' => 'trim',
'default' => ''
],
'defaultExtras' => 'richtext:rte_transform[mode=ts_css]'
],
'description' => [
'exclude' => true,
'label' => 'Description',
'config' => [
'type' => 'text',
'enableRichtext' => 'true',
'eval' => 'trim',
'default' => ''
],
'defaultExtras' => 'richtext:rte_transform[mode=ts_css]'
],
'highlights' => [
'exclude' => true,
'label' => 'Highlights',
'config' => [
'type' => 'text',
'enableRichtext' => 'true',
'eval' => 'trim',
'default' => ''
],
'defaultExtras' => 'richtext:rte_transform[mode=ts_css]'
],
'shortcut' => [
'exclude' => true,
'label' => 'Does this Product have a custom Page',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'shortcutpid' => [
'exclude' => false,
'label' => 'Page ID',
'description' => 'PID of the custom page',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'legacy' => [
'exclude' => true,
'label' => 'Is this a legacy produc?',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'supportproduct' => [
'exclude' => true,
'label' => 'Is this a support product?',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'subproduct' => [
'exclude' => true,
'label' => 'Product is a sub-products',
'description' => 'This Product will not be shown as a main product',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'relatedprodukt' => [
'exclude' => true,
'label' => 'Related Products',
'description' => 'Select related products for this product',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_vitec_domain_model_product', // Reference the same table
'MM' => 'tx_vitec_product_related_mm', // Define the MM table for the relation
'size' => 10,
'maxitems' => 9999,
'minitems' => 0,
'enableMultiSelectFilterTextfield' => true, // Optional: Adds a search box for filtering
],
],
'productimage' => [
'exclude' => true,
'label' => 'Product Image(s)',
'config' => [
'type' => 'inline',
'foreign_table' => 'sys_file_reference',
'foreign_field' => 'uid_foreign',
'foreign_sortby' => 'sorting_foreign',
'foreign_table_field' => 'tablenames',
'foreign_match_fields' => [
'fieldname' => 'productimage',
],
'appearance' => [
'collapseAll' => true,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => true,
'showPossibleLocalizationRecords' => true,
'showAllLocalizationLink' => true,
],
'behaviour' => [
'allowLanguageSynchronization' => true,
],
'filter' => [
[
'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren',
'parameters' => [
'allowedFileExtensions' => 'jpg,jpeg,png,gif',
],
],
],
'maxitems' => 10,
'minitems' => 0,
],
],
'ogimage' => [
'exclude' => true,
'label' => 'Open Graph Image',
'config' => [
'type' => 'inline',
'foreign_table' => 'sys_file_reference',
'foreign_field' => 'uid_foreign',
'foreign_sortby' => 'sorting_foreign',
'foreign_table_field' => 'tablenames',
'foreign_match_fields' => [
'fieldname' => 'ogimage',
],
'appearance' => [
'collapseAll' => true,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => true,
'showPossibleLocalizationRecords' => true,
'showAllLocalizationLink' => true,
],
'behaviour' => [
'allowLanguageSynchronization' => true,
],
'filter' => [
[
'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren',
'parameters' => [
'allowedFileExtensions' => 'jpg,jpeg,png,gif,webp',
],
],
],
'maxitems' => 1, // Allow only one image
'minitems' => 0,
],
],
'contentelement' => [
'exclude' => true,
'label' => 'Select Content Element for Key Features Section',
'config' => [
'type' => 'input',
'renderType' => 'inputLink',
'softref' => 'typolink',
'eval' => 'trim',
'fieldControl' => [
'linkPopup' => [
'options' => [
'title' => 'Select Content Element',
'blindLinkOptions' => 'mail,folder,url', // Disable unnecessary link types
'blindLinkFields' => 'class,params', // Disable unnecessary fields
],
],
],
],
],
'contentelementcta' => [
'exclude' => true,
'label' => 'Select Content Element for CTA Section',
'config' => [
'type' => 'input',
'renderType' => 'inputLink',
'softref' => 'typolink',
'eval' => 'trim',
'fieldControl' => [
'linkPopup' => [
'options' => [
'title' => 'Select Content Element',
'blindLinkOptions' => 'mail,folder,url', // Disable unnecessary link types
'blindLinkFields' => 'class,params', // Disable unnecessary fields
],
],
],
],
],
/* ----------------------------------------------------- */
],
];

View File

@@ -0,0 +1,576 @@
<?php
return [
'ctrl' => [
'title' => 'VITEC Product',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'versioningWS' => true,
'languageField' => 'sys_language_uid',
'transOrigPointerField' => 'l10n_parent',
'transOrigDiffSourceField' => 'l10n_diffsource',
'delete' => 'deleted',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'title,slug',
'iconfile' => 'EXT:vitec/Resources/Public/Icons/tx_vitec_domain_model_product.gif',
'security' => [
'ignorePageTypeRestriction' => true,
],
],
'types' => [
'1' => ['showitem' => 'title, subtitle, slug, teaser, description, applications, highlights, contentelement, contentelementcta, downloads,
--div--;SEO, seotitle, urltitle, seometa, keywords, structureddata,
--div--;Images and Videos, productimage, ogimage, video, videofile,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories, categories,
--div--;Visibility, hideonapp, hideonwebsite, hideondatasheets, hideonproducts, shortcut, shortcutpid,
--div--;Misc, legacy, supportproduct, subproduct, relatedprodukt,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime'],
],
'columns' => [
'sys_language_uid' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
'config' => [
'type' => 'language',
],
],
'l10n_parent' => [
'displayCond' => 'FIELD:sys_language_uid:>:0',
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'default' => 0,
'items' => [
['', 0],
],
'foreign_table' => 'tx_vitec_domain_model_product',
'foreign_table_where' => 'AND {#tx_vitec_domain_model_product}.{#pid}=###CURRENT_PID### AND {#tx_vitec_domain_model_product}.{#sys_language_uid} IN (-1,0)',
],
],
'l10n_diffsource' => [
'config' => [
'type' => 'passthrough',
],
],
'hidden' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => true
]
],
],
],
'starttime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
'config' => [
'type' => 'input',
'renderType' => 'inputDateTime',
'eval' => 'datetime,int',
'default' => 0,
'behaviour' => [
'allowLanguageSynchronization' => true
]
],
],
'endtime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
'config' => [
'type' => 'input',
'renderType' => 'inputDateTime',
'eval' => 'datetime,int',
'default' => 0,
'range' => [
'upper' => mktime(0, 0, 0, 1, 1, 2038)
],
'behaviour' => [
'allowLanguageSynchronization' => true
]
],
],
'categories' => [
'config' => [
'type' => 'category'
]
],
'title' => [
'exclude' => false,
'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.title',
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.title.description',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'required' => true,
'default' => ''
],
],
'slug' => [
'exclude' => false,
'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.slug',
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.slug.description',
'config' => [
'type' => 'slug',
'size' => 50,
'generatorOptions' => [
'fields' => ['title'], // TODO: adjust this field to the one you want to use
'fieldSeparator' => '-',
'replacements' => [
'/' => '',
],
],
'fallbackCharacter' => '-',
'eval' => 'uniqueInPid',
],
],
'seotitle' => [
'exclude' => false,
'label' => 'SEO-Title',
'description' => 'SEO specific title, used for sharing options',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'urltitle' => [
'exclude' => false,
'label' => 'URL-Title',
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.urltitle.description',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'video' => [
'exclude' => false,
'label' => 'Product Video - just the Youtube Clip ID',
'description' => 'Product Video - just the Youtube Clip ID',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'downloads' => [
'exclude' => true,
'label' => 'Downloads',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_vitec_domain_model_download',
'MM' => 'tx_vitec_product_download_mm', // Define the MM table for the relation
'size' => 10,
'maxitems' => 9999,
'minitems' => 0,
'enableMultiSelectFilterTextfield' => true, // Optional: Adds a search box for filtering
],
],
'structureddata' => [
'exclude' => true,
'label' => 'Structured Data in JSON Format',
'config' => [
'type' => 'text',
'cols' => 40,
'rows' => 15,
'eval' => 'trim'
]
],
'keywords' => [
'exclude' => true,
'label' => 'Keywords',
'description' => 'Keywords for SEO, separated by commas',
'config' => [
'type' => 'text',
'cols' => 40,
'rows' => 15,
'eval' => 'trim'
]
],
'seometa' => [
'exclude' => true,
'label' => 'SEO Meta Description',
'description' => 'SEO specific meta description, used for sharing options',
'config' => [
'type' => 'text',
'cols' => 40,
'rows' => 15,
'eval' => 'trim'
]
],
'teaser' => [
'exclude' => true,
'label' => 'Short Teaser (optional)',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'subtitle' => [
'exclude' => true,
'label' => 'Subtitle',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'hideonapp' => [
'exclude' => true,
'label' => 'If checked - Product will not be shown on App',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'hideonwebsite' => [
'exclude' => true,
'label' => 'If checked - Product will not be shown on Website',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'hideonproducts' => [
'exclude' => true,
'label' => 'If checked - Product will not be shown on Product Section',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'hideondatasheets' => [
'exclude' => true,
'label' => 'If checked - Product will not be shown on Datasheets Page',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'applications' => [
'exclude' => true,
'label' => 'Applications',
'config' => [
'type' => 'text',
'enableRichtext' => 'true',
'eval' => 'trim',
'default' => ''
],
'defaultExtras' => 'richtext:rte_transform[mode=ts_css]'
],
'description' => [
'exclude' => true,
'label' => 'Description',
'config' => [
'type' => 'text',
'enableRichtext' => 'true',
'eval' => 'trim',
'default' => ''
],
'defaultExtras' => 'richtext:rte_transform[mode=ts_css]'
],
'highlights' => [
'exclude' => true,
'label' => 'Highlights',
'config' => [
'type' => 'text',
'enableRichtext' => 'true',
'eval' => 'trim',
'default' => ''
],
'defaultExtras' => 'richtext:rte_transform[mode=ts_css]'
],
'shortcut' => [
'exclude' => true,
'label' => 'Does this Product have a custom Page',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'shortcutpid' => [
'exclude' => false,
'label' => 'Page ID',
'description' => 'PID of the custom page',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => ''
],
],
'legacy' => [
'exclude' => true,
'label' => 'Is this a legacy produc?',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'supportproduct' => [
'exclude' => true,
'label' => 'Is this a support product?',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'subproduct' => [
'exclude' => true,
'label' => 'Product is a sub-products',
'description' => 'This Product will not be shown as a main product',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
0 => '',
1 => '',
'invertStateDisplay' => false
]
],
],
],
'relatedprodukt' => [
'exclude' => true,
'label' => 'Related Products',
'description' => 'Select related products for this product',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_vitec_domain_model_product', // Reference the same table
'MM' => 'tx_vitec_product_related_mm', // Define the MM table for the relation
'size' => 10,
'maxitems' => 9999,
'minitems' => 0,
'enableMultiSelectFilterTextfield' => true, // Optional: Adds a search box for filtering
],
],
'productimage' => [
'exclude' => true,
'label' => 'Product Image(s)',
'config' => [
'type' => 'inline',
'foreign_table' => 'sys_file_reference',
'foreign_field' => 'uid_foreign',
'foreign_sortby' => 'sorting_foreign',
'foreign_table_field' => 'tablenames',
'foreign_match_fields' => [
'fieldname' => 'productimage',
],
'appearance' => [
'collapseAll' => true,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => true,
'showPossibleLocalizationRecords' => true,
'showAllLocalizationLink' => true,
],
'behaviour' => [
'allowLanguageSynchronization' => true,
],
'filter' => [
[
'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren',
'parameters' => [
'allowedFileExtensions' => 'jpg,jpeg,png,gif',
],
],
],
'maxitems' => 10,
'minitems' => 0,
],
],
'ogimage' => [
'exclude' => true,
'label' => 'Open Graph Image',
'config' => [
'type' => 'inline',
'foreign_table' => 'sys_file_reference',
'foreign_field' => 'uid_foreign',
'foreign_sortby' => 'sorting_foreign',
'foreign_table_field' => 'tablenames',
'foreign_match_fields' => [
'fieldname' => 'ogimage',
],
'appearance' => [
'collapseAll' => true,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => true,
'showPossibleLocalizationRecords' => true,
'showAllLocalizationLink' => true,
],
'behaviour' => [
'allowLanguageSynchronization' => true,
],
'filter' => [
[
'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren',
'parameters' => [
'allowedFileExtensions' => 'jpg,jpeg,png,gif,webp',
],
],
],
'maxitems' => 1, // Allow only one image
'minitems' => 0,
],
],
'videofile' => [
'exclude' => true,
'label' => 'Video File (Upload or Local)',
'config' => [
'type' => 'inline',
'foreign_table' => 'sys_file_reference',
'foreign_field' => 'uid_foreign',
'foreign_sortby' => 'sorting_foreign',
'foreign_table_field' => 'tablenames',
'foreign_match_fields' => [
'fieldname' => 'videofile',
],
'appearance' => [
'collapseAll' => true,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => true,
'showPossibleLocalizationRecords' => true,
'showAllLocalizationLink' => true,
],
'behaviour' => [
'allowLanguageSynchronization' => true,
],
'filter' => [
[
'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren',
'parameters' => [
'allowedFileExtensions' => 'mp4,webm,ogv,mov,m4v',
],
],
],
'maxitems' => 1,
'minitems' => 0,
],
],
'contentelement' => [
'exclude' => true,
'label' => 'Select Content Element for Key Features Section',
'config' => [
'type' => 'input',
'renderType' => 'inputLink',
'softref' => 'typolink',
'eval' => 'trim',
'fieldControl' => [
'linkPopup' => [
'options' => [
'title' => 'Select Content Element',
'blindLinkOptions' => 'mail,folder,url', // Disable unnecessary link types
'blindLinkFields' => 'class,params', // Disable unnecessary fields
],
],
],
],
],
'contentelementcta' => [
'exclude' => true,
'label' => 'Select Content Element for CTA Section',
'config' => [
'type' => 'input',
'renderType' => 'inputLink',
'softref' => 'typolink',
'eval' => 'trim',
'fieldControl' => [
'linkPopup' => [
'options' => [
'title' => 'Select Content Element',
'blindLinkOptions' => 'mail,folder,url', // Disable unnecessary link types
'blindLinkFields' => 'class,params', // Disable unnecessary fields
],
],
],
],
],
/* ----------------------------------------------------- */
],
];

View File

View File

View File

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

View File

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 278 B

View File

Before

Width:  |  Height:  |  Size: 201 B

After

Width:  |  Height:  |  Size: 201 B

View File

Before

Width:  |  Height:  |  Size: 203 B

After

Width:  |  Height:  |  Size: 203 B

View File

Before

Width:  |  Height:  |  Size: 202 B

After

Width:  |  Height:  |  Size: 202 B

View File

Before

Width:  |  Height:  |  Size: 447 B

After

Width:  |  Height:  |  Size: 447 B

0
packages/vitec/ext_localconf.php Normal file → Executable file
View File

0
packages/vitec/ext_tables.sql Normal file → Executable file
View File

View File

@@ -0,0 +1,171 @@
CREATE TABLE tx_vitec_domain_model_product (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
deleted tinyint(4) DEFAULT '0' NOT NULL,
hidden tinyint(4) DEFAULT '0' NOT NULL,
starttime int(11) DEFAULT '0' NOT NULL,
endtime int(11) DEFAULT '0' NOT NULL,
title varchar(255) DEFAULT '' NOT NULL,
slug varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY (uid),
urltitle varchar(255) DEFAULT '' NOT NULL,
seotitle varchar(255) DEFAULT '' NOT NULL,
seometa text,
keywords text,
productimage int(11) DEFAULT '0' NOT NULL,
structureddata text,
teaser varchar(255) DEFAULT '' NOT NULL,
subtitle varchar(255) DEFAULT '' NOT NULL,
applications text,
highlights text,
description text,
image INTEGER,
relatedimage INTEGER,
ogimage INTEGER,
legacy tinyint(4) DEFAULT '0' NOT NULL,
supportproduct tinyint(4) DEFAULT '0' NOT NULL,
subproduct tinyint(4) DEFAULT '0' NOT NULL,
cta varchar(255) DEFAULT '' NOT NULL,
sorting1 smallint(5) NOT NULL,
sorting2 smallint(5) NOT NULL,
sorting3 smallint(5) NOT NULL,
sorting4 smallint(5) NOT NULL,
sorting5 smallint(5) NOT NULL,
links text,
hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL,
hideondatasheets smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonproducts smallint(5) unsigned DEFAULT '0' NOT NULL,
shortcut tinyint(4) DEFAULT '0' NOT NULL,
shortcutpid smallint(5) NOT NULL,
video varchar(255) DEFAULT '' NOT NULL,
key1 varchar(255) DEFAULT '' NOT NULL,
key2 varchar(255) DEFAULT '' NOT NULL,
key3 varchar(255) DEFAULT '' NOT NULL,
apptext1 varchar(255) DEFAULT '' NOT NULL,
apptext2 varchar(255) DEFAULT '' NOT NULL,
apptext3 varchar(255) DEFAULT '' NOT NULL,
productlayout int(11) DEFAULT '0' NOT NULL,
contentelement varchar(255) DEFAULT '' NOT NULL,
contentelementcta varchar(255) DEFAULT '' NOT NULL,
KEY parent (pid)
);
CREATE TABLE tx_vitec_domain_model_download (
title varchar(255) DEFAULT '' NOT NULL,
slug varchar(255) DEFAULT '' NOT NULL,
teaser varchar(255) NOT NULL DEFAULT '',
keywords varchar(255) NOT NULL DEFAULT '',
description text,
file int(11) unsigned NOT NULL DEFAULT '0',
sort1 varchar(255) NOT NULL DEFAULT '',
sort2 varchar(255) NOT NULL DEFAULT '',
sort3 varchar(255) NOT NULL DEFAULT '',
private_download smallint(1) unsigned NOT NULL DEFAULT '0',
hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL,
hideondatasheets smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonproducts smallint(5) unsigned DEFAULT '0' NOT NULL,
icon varchar(255) NOT NULL DEFAULT '',
filepath varchar(255) NOT NULL DEFAULT '',
fileprefix varchar(255) NOT NULL DEFAULT '',
useolddl smallint(1) unsigned NOT NULL DEFAULT '0'
);
CREATE TABLE tx_vitec_product_download_mm (
uid_local int(11) DEFAULT '0' NOT NULL,
uid_foreign int(11) DEFAULT '0' NOT NULL,
sorting int(11) DEFAULT '0' NOT NULL,
sorting_foreign int(11) DEFAULT '0' NOT NULL,
KEY uid_local (uid_local),
KEY uid_foreign (uid_foreign)
);
CREATE TABLE tx_vitec_product_related_mm (
uid_local INT(11) NOT NULL,
uid_foreign INT(11) NOT NULL,
sorting INT(11) DEFAULT '0' NOT NULL,
PRIMARY KEY (uid_local, uid_foreign)
);
CREATE TABLE tx_vitec_domain_model_usecase (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
deleted tinyint(4) DEFAULT '0' NOT NULL,
hidden tinyint(4) DEFAULT '0' NOT NULL,
starttime int(11) DEFAULT '0' NOT NULL,
endtime int(11) DEFAULT '0' NOT NULL,
title varchar(255) DEFAULT '' NOT NULL,
slug varchar(255) DEFAULT '' NOT NULL,
teaser varchar(255) DEFAULT '' NOT NULL,
subtitle varchar(255) DEFAULT '' NOT NULL,
description text,
caseimage INTEGER,
logoimage INTEGER,
singlepid varchar(255) DEFAULT '' NOT NULL,
hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL,
PRIMARY KEY (uid),
KEY parent (pid)
);
CREATE TABLE tx_vitec_domain_model_solution (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
deleted tinyint(4) DEFAULT '0' NOT NULL,
hidden tinyint(4) DEFAULT '0' NOT NULL,
starttime int(11) DEFAULT '0' NOT NULL,
endtime int(11) DEFAULT '0' NOT NULL,
sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob,
title varchar(255) DEFAULT '' NOT NULL,
subtitle varchar(255) DEFAULT '' NOT NULL,
teaser varchar(255) DEFAULT '' NOT NULL,
description text,
image int(11) DEFAULT '0' NOT NULL,
PRIMARY KEY (uid),
KEY parent (pid),
KEY language (l10n_parent, sys_language_uid)
);
CREATE TABLE tx_vitec_domain_model_market (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
deleted tinyint(4) DEFAULT '0' NOT NULL,
hidden tinyint(4) DEFAULT '0' NOT NULL,
starttime int(11) DEFAULT '0' NOT NULL,
endtime int(11) DEFAULT '0' NOT NULL,
sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob,
title varchar(255) DEFAULT '' NOT NULL,
subtitle varchar(255) DEFAULT '' NOT NULL,
teaser varchar(255) DEFAULT '' NOT NULL,
description text,
image int(11) DEFAULT '0' NOT NULL,
PRIMARY KEY (uid),
KEY parent (pid),
KEY language (l10n_parent, sys_language_uid)
);
ALTER TABLE sys_category
ADD class VARCHAR(255) DEFAULT '' NOT NULL,
ADD filetype VARCHAR(255) DEFAULT '' NOT NULL,
ADD type VARCHAR(255) DEFAULT '' NOT NULL;
CREATE TABLE tt_content (
tx_vitec_bg_variant VARCHAR(20) DEFAULT 'none' NOT NULL
);

View File

@@ -0,0 +1,172 @@
CREATE TABLE tx_vitec_domain_model_product (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
deleted tinyint(4) DEFAULT '0' NOT NULL,
hidden tinyint(4) DEFAULT '0' NOT NULL,
starttime int(11) DEFAULT '0' NOT NULL,
endtime int(11) DEFAULT '0' NOT NULL,
title varchar(255) DEFAULT '' NOT NULL,
slug varchar(255) DEFAULT '' NOT NULL,
PRIMARY KEY (uid),
urltitle varchar(255) DEFAULT '' NOT NULL,
seotitle varchar(255) DEFAULT '' NOT NULL,
seometa text,
keywords text,
productimage int(11) DEFAULT '0' NOT NULL,
structureddata text,
teaser varchar(255) DEFAULT '' NOT NULL,
subtitle varchar(255) DEFAULT '' NOT NULL,
applications text,
highlights text,
description text,
image INTEGER,
relatedimage INTEGER,
ogimage INTEGER,
legacy tinyint(4) DEFAULT '0' NOT NULL,
supportproduct tinyint(4) DEFAULT '0' NOT NULL,
subproduct tinyint(4) DEFAULT '0' NOT NULL,
cta varchar(255) DEFAULT '' NOT NULL,
sorting1 smallint(5) NOT NULL,
sorting2 smallint(5) NOT NULL,
sorting3 smallint(5) NOT NULL,
sorting4 smallint(5) NOT NULL,
sorting5 smallint(5) NOT NULL,
links text,
hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL,
hideondatasheets smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonproducts smallint(5) unsigned DEFAULT '0' NOT NULL,
shortcut tinyint(4) DEFAULT '0' NOT NULL,
shortcutpid smallint(5) NOT NULL,
video varchar(255) DEFAULT '' NOT NULL,
key1 varchar(255) DEFAULT '' NOT NULL,
key2 varchar(255) DEFAULT '' NOT NULL,
key3 varchar(255) DEFAULT '' NOT NULL,
apptext1 varchar(255) DEFAULT '' NOT NULL,
apptext2 varchar(255) DEFAULT '' NOT NULL,
apptext3 varchar(255) DEFAULT '' NOT NULL,
productlayout int(11) DEFAULT '0' NOT NULL,
contentelement varchar(255) DEFAULT '' NOT NULL,
contentelementcta varchar(255) DEFAULT '' NOT NULL,
videofile int(11) unsigned DEFAULT '0' NOT NULL,
KEY parent (pid)
);
CREATE TABLE tx_vitec_domain_model_download (
title varchar(255) DEFAULT '' NOT NULL,
slug varchar(255) DEFAULT '' NOT NULL,
teaser varchar(255) NOT NULL DEFAULT '',
keywords varchar(255) NOT NULL DEFAULT '',
description text,
file int(11) unsigned NOT NULL DEFAULT '0',
sort1 varchar(255) NOT NULL DEFAULT '',
sort2 varchar(255) NOT NULL DEFAULT '',
sort3 varchar(255) NOT NULL DEFAULT '',
private_download smallint(1) unsigned NOT NULL DEFAULT '0',
hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL,
hideondatasheets smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonproducts smallint(5) unsigned DEFAULT '0' NOT NULL,
icon varchar(255) NOT NULL DEFAULT '',
filepath varchar(255) NOT NULL DEFAULT '',
fileprefix varchar(255) NOT NULL DEFAULT '',
useolddl smallint(1) unsigned NOT NULL DEFAULT '0'
);
CREATE TABLE tx_vitec_product_download_mm (
uid_local int(11) DEFAULT '0' NOT NULL,
uid_foreign int(11) DEFAULT '0' NOT NULL,
sorting int(11) DEFAULT '0' NOT NULL,
sorting_foreign int(11) DEFAULT '0' NOT NULL,
KEY uid_local (uid_local),
KEY uid_foreign (uid_foreign)
);
CREATE TABLE tx_vitec_product_related_mm (
uid_local INT(11) NOT NULL,
uid_foreign INT(11) NOT NULL,
sorting INT(11) DEFAULT '0' NOT NULL,
PRIMARY KEY (uid_local, uid_foreign)
);
CREATE TABLE tx_vitec_domain_model_usecase (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
deleted tinyint(4) DEFAULT '0' NOT NULL,
hidden tinyint(4) DEFAULT '0' NOT NULL,
starttime int(11) DEFAULT '0' NOT NULL,
endtime int(11) DEFAULT '0' NOT NULL,
title varchar(255) DEFAULT '' NOT NULL,
slug varchar(255) DEFAULT '' NOT NULL,
teaser varchar(255) DEFAULT '' NOT NULL,
subtitle varchar(255) DEFAULT '' NOT NULL,
description text,
caseimage INTEGER,
logoimage INTEGER,
singlepid varchar(255) DEFAULT '' NOT NULL,
hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL,
PRIMARY KEY (uid),
KEY parent (pid)
);
CREATE TABLE tx_vitec_domain_model_solution (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
deleted tinyint(4) DEFAULT '0' NOT NULL,
hidden tinyint(4) DEFAULT '0' NOT NULL,
starttime int(11) DEFAULT '0' NOT NULL,
endtime int(11) DEFAULT '0' NOT NULL,
sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob,
title varchar(255) DEFAULT '' NOT NULL,
subtitle varchar(255) DEFAULT '' NOT NULL,
teaser varchar(255) DEFAULT '' NOT NULL,
description text,
image int(11) DEFAULT '0' NOT NULL,
PRIMARY KEY (uid),
KEY parent (pid),
KEY language (l10n_parent, sys_language_uid)
);
CREATE TABLE tx_vitec_domain_model_market (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
deleted tinyint(4) DEFAULT '0' NOT NULL,
hidden tinyint(4) DEFAULT '0' NOT NULL,
starttime int(11) DEFAULT '0' NOT NULL,
endtime int(11) DEFAULT '0' NOT NULL,
sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob,
title varchar(255) DEFAULT '' NOT NULL,
subtitle varchar(255) DEFAULT '' NOT NULL,
teaser varchar(255) DEFAULT '' NOT NULL,
description text,
image int(11) DEFAULT '0' NOT NULL,
PRIMARY KEY (uid),
KEY parent (pid),
KEY language (l10n_parent, sys_language_uid)
);
ALTER TABLE sys_category
ADD class VARCHAR(255) DEFAULT '' NOT NULL,
ADD filetype VARCHAR(255) DEFAULT '' NOT NULL,
ADD type VARCHAR(255) DEFAULT '' NOT NULL;
CREATE TABLE tt_content (
tx_vitec_bg_variant VARCHAR(20) DEFAULT 'none' NOT NULL
);