Initial Commit
This commit is contained in:
1 parent
4f495177e4
commit
b6d2142214
166 files changed
+13952
-178
No files matched your search
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Evomedien\Vitec\Components;
|
||||
namespace Evomedien\Vitec\Controller;
|
||||
|
||||
use TYPO3Fluid\Fluid\Core\Component\AbstractComponentCollection;
|
||||
use TYPO3Fluid\Fluid\View\TemplatePaths;
|
||||
|
||||
final class ComponentCollection extends AbstractComponentCollection
|
||||
{
|
||||
public function getTemplatePaths(): TemplatePaths
|
||||
{
|
||||
$templatePaths = new TemplatePaths();
|
||||
$templatePaths->setTemplateRootPaths([
|
||||
'EXT:Vitec/Resources/Private/Components/',
|
||||
]);
|
||||
return $templatePaths;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Controller;
|
||||
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Resource\FileRepository;
|
||||
use TYPO3\CMS\Core\Mail\MailerInterface;
|
||||
use TYPO3\CMS\Core\Mail\FluidEmail;
|
||||
use TYPO3\CMS\Core\Http\ResponseFactory;
|
||||
use TYPO3\CMS\Core\Http\Stream;
|
||||
use TYPO3\CMS\Extbase\Http\ForwardResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ResponseFactoryInterface;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
|
||||
use Evomedien\Vitec\Domain\Repository\DownloadRepository;
|
||||
use Evomedien\Vitec\Domain\Repository\ProductRepository;
|
||||
use Evomedien\Vitec\Domain\Model\Download;
|
||||
|
||||
/**
|
||||
* 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) 2021
|
||||
*/
|
||||
|
||||
/**
|
||||
* DownloadController
|
||||
*/
|
||||
class DownloadController extends ActionController
|
||||
{
|
||||
protected DownloadRepository $downloadRepository;
|
||||
protected ProductRepository $productRepository;
|
||||
protected MailerInterface $mailer;
|
||||
protected ResponseFactoryInterface $responseFactory;
|
||||
|
||||
public function __construct(
|
||||
DownloadRepository $downloadRepository,
|
||||
ProductRepository $productRepository,
|
||||
MailerInterface $mailer,
|
||||
ResponseFactoryInterface $responseFactory
|
||||
) {
|
||||
$this->downloadRepository = $downloadRepository;
|
||||
$this->productRepository = $productRepository;
|
||||
$this->mailer = $mailer;
|
||||
$this->responseFactory = $responseFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* action list
|
||||
*/
|
||||
public function listAction(): ResponseInterface
|
||||
{
|
||||
$downloads = $this->downloadRepository->findAll();
|
||||
$this->view->assign('downloads', $downloads);
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* action show
|
||||
*/
|
||||
public function showAction(): ResponseInterface
|
||||
{
|
||||
$products = $this->productRepository->findAlphabetically(false, false);
|
||||
$ps = [];
|
||||
$used = [];
|
||||
|
||||
foreach ($products as $p) {
|
||||
if (!in_array($p->getUid(), $used)) {
|
||||
$scat = $p->getProductsubcategory()->toArray();
|
||||
$cat = $p->getProductcategory()->toArray();
|
||||
if (!empty($scat)) {
|
||||
$ps[$scat[0]->getTitle()][] = $p;
|
||||
} elseif (!empty($cat)) {
|
||||
$ps[$cat[0]->getTitle()][] = $p;
|
||||
}
|
||||
$used[] = $p->getUid();
|
||||
}
|
||||
}
|
||||
ksort($ps);
|
||||
|
||||
$this->view->assign('settings', $this->settings);
|
||||
$this->view->assign('products', $ps);
|
||||
// $dcs = $this->dcRepository->fetchForWebsite();
|
||||
// $this->view->assign('dcs', $dcs);
|
||||
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* action directdownload
|
||||
*/
|
||||
public function directdownloadAction(Download $download): ResponseInterface
|
||||
{
|
||||
$json = $download->getForJSON();
|
||||
$file = Environment::getProjectPath() . $json['file'];
|
||||
|
||||
if (!file_exists($file)) {
|
||||
throw new \Exception('File not found', 404);
|
||||
}
|
||||
|
||||
$response = $this->responseFactory->createResponse()
|
||||
->withHeader('Content-Type', 'application/pdf')
|
||||
->withHeader('Content-Disposition', 'inline; filename="' . basename($file) . '"')
|
||||
->withHeader('Content-Transfer-Encoding', 'binary')
|
||||
->withHeader('Accept-Ranges', 'bytes')
|
||||
->withHeader('Content-Length', (string)filesize($file))
|
||||
->withHeader('Pragma', 'public')
|
||||
->withHeader('Expires', '0')
|
||||
->withHeader('Cache-Control', 'must-revalidate');
|
||||
|
||||
$stream = new Stream(fopen($file, 'rb'));
|
||||
return $response->withBody($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* action ac
|
||||
*/
|
||||
public function acAction(): ResponseInterface
|
||||
{
|
||||
$downloads = [];
|
||||
$request = $this->request;
|
||||
|
||||
if (!empty($request->getQueryParams()['dc'])) {
|
||||
$downloads = $this->downloadRepository->findByCategory($request->getQueryParams()['dc'], 1);
|
||||
} elseif (!empty($request->getQueryParams()['k'])) {
|
||||
$downloads = $this->downloadRepository->search($request->getQueryParams()['k'], 1);
|
||||
} elseif (!empty($request->getQueryParams()['p'])) {
|
||||
$downloads = $this->downloadRepository->findByProduct($request->getQueryParams()['p'], 1);
|
||||
$product = $this->productRepository->findByUid($request->getQueryParams()['p']);
|
||||
$this->view->assign('product', $product);
|
||||
}
|
||||
|
||||
$this->downloadInfo($downloads);
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
private function downloadInfo($downloads): void
|
||||
{
|
||||
if (!empty($downloads)) {
|
||||
$info = [];
|
||||
foreach ($downloads as $d) {
|
||||
$file = $d->getFile();
|
||||
if (!empty($file)) {
|
||||
$fileResourceReference = GeneralUtility::makeInstance(ResourceFactory::class)
|
||||
->getFileReferenceObject($file->getUid());
|
||||
$file = urldecode($fileResourceReference->getOriginalFile()->getPublicUrl());
|
||||
$i = pathinfo($file);
|
||||
$i['size'] = $this->human_filesize(filesize($file));
|
||||
$info[$d->getUid()] = $i;
|
||||
}
|
||||
}
|
||||
$this->view->assign('info', $info);
|
||||
}
|
||||
$this->view->assign('downloads', $downloads);
|
||||
}
|
||||
|
||||
private function human_filesize($bytes, $decimals = 2): string
|
||||
{
|
||||
$sz = 'BKMGTP';
|
||||
$factor = floor((strlen(strval($bytes)) - 1) / 3);
|
||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
|
||||
}
|
||||
|
||||
/**
|
||||
* action download
|
||||
*/
|
||||
public function downloadAction(Download $download): ResponseInterface
|
||||
{
|
||||
return $this->directdownloadAction($download);
|
||||
}
|
||||
|
||||
public function combineddlAction(string $files): ResponseInterface
|
||||
{
|
||||
$ids = explode(',', $files);
|
||||
$downloads = $this->downloadRepository->findByUids($ids);
|
||||
$file = $this->createZip(md5($files), $downloads, null);
|
||||
|
||||
if ($file !== false) {
|
||||
$response = $this->responseFactory->createResponse()
|
||||
->withHeader('Content-disposition', 'attachment; filename=Vitec.zip')
|
||||
->withHeader('Content-type', 'application/zip')
|
||||
->withHeader('Content-Length', (string)filesize($file));
|
||||
|
||||
$stream = new Stream(fopen($file, 'rb'));
|
||||
unlink($file);
|
||||
return $response->withBody($stream);
|
||||
}
|
||||
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* action datasheets
|
||||
*/
|
||||
public function datasheetsAction(): ResponseInterface
|
||||
{
|
||||
$productso = $this->productRepository->fetchForDatasheets();
|
||||
$sheets = [];
|
||||
$faved = [];
|
||||
$faves = [];
|
||||
$request = $this->request;
|
||||
|
||||
if (!empty($_COOKIE['datasheet-faves'])) {
|
||||
$faved = json_decode($_COOKIE['datasheet-faves']);
|
||||
}
|
||||
|
||||
if (!empty($request->getParsedBody()['download'])) {
|
||||
$downloads = $this->downloadRepository->findByUids($faved);
|
||||
$file = $this->createZip($_COOKIE['datasheet-faves'], $downloads, null);
|
||||
if ($file !== false) {
|
||||
$response = $this->responseFactory->createResponse()
|
||||
->withHeader('Content-disposition', 'attachment; filename=Vitec.zip')
|
||||
->withHeader('Content-type', 'application/zip')
|
||||
->withHeader('Content-Length', (string)filesize($file));
|
||||
|
||||
$stream = new Stream(fopen($file, 'rb'));
|
||||
unlink($file);
|
||||
return $response->withBody($stream);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($productso)) {
|
||||
foreach ($productso as $p) {
|
||||
$downloads = $p->getDownload();
|
||||
foreach ($downloads as $d) {
|
||||
$isSheet = false;
|
||||
foreach ($d->getType() as $t) {
|
||||
// filter download type ids
|
||||
if ($t->getUid() == 1 || $t->getUid() == 3) {
|
||||
$isSheet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($isSheet) {
|
||||
$cat = null;
|
||||
$maincategorysort = 0;
|
||||
foreach ($p->getProductcategory() as $c) {
|
||||
$cat = $c->getTitle();
|
||||
$maincategorysort = $c->getSort3();
|
||||
break;
|
||||
}
|
||||
$subcat = null;
|
||||
$subcatsort = 0;
|
||||
foreach ($p->getProductsubcategory() as $s) {
|
||||
$subcat = $s->getTitle();
|
||||
$subcatsort = $s->getSort3();
|
||||
break;
|
||||
}
|
||||
$isFaved = false;
|
||||
if (in_array($d->getUid(), $faved)) {
|
||||
$faves[] = [
|
||||
'product' => $p,
|
||||
'datasheet' => $d
|
||||
];
|
||||
$isFaved = true;
|
||||
}
|
||||
$sheets[] = [
|
||||
'isfaved' => $isFaved,
|
||||
'category' => $cat,
|
||||
'sort' => $maincategorysort,
|
||||
'subcategory' => $subcat,
|
||||
'subcategorysort' => strval($subcatsort),
|
||||
'product' => $p,
|
||||
'datasheet' => $d
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usort($sheets, function ($a, $b) {
|
||||
if ($a["sort"] === $b["sort"]) {
|
||||
if ($a["subcategorysort"] === $b["subcategorysort"]) {
|
||||
return strcmp($a["product"]->getTitle(), $b["product"]->getTitle());
|
||||
} else {
|
||||
return strcmp($a["subcategorysort"], $b["subcategorysort"]);
|
||||
}
|
||||
}
|
||||
return strcmp(strval($a["sort"]), strval($b["sort"]));
|
||||
});
|
||||
|
||||
$this->view->assign('sheets', $sheets);
|
||||
$this->view->assign('faves', $faves);
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* action index
|
||||
*/
|
||||
public function indexAction(): ResponseInterface
|
||||
{
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
private function createZip($req, $downloads, $stories): string|false
|
||||
{
|
||||
$zip = new \ZipArchive();
|
||||
$tmp_file = '/tmp/' . sha1($req) . '.zip';
|
||||
|
||||
if ($zip->open($tmp_file, \ZipArchive::CREATE)) {
|
||||
if (!empty($downloads)) {
|
||||
foreach ($downloads as $download) {
|
||||
$f = $download->getFilepfad();
|
||||
if ($f !== false) {
|
||||
$zip->addFile($f, basename($f));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($stories)) {
|
||||
foreach ($stories as $story) {
|
||||
$f = $story['pdf'];
|
||||
if ($f !== false) {
|
||||
$zip->addFile($f, basename($f));
|
||||
}
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
return $tmp_file;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* action collection
|
||||
*/
|
||||
public function collectionAction(): ResponseInterface
|
||||
{
|
||||
$downloads = [];
|
||||
$downloadids = [];
|
||||
$stories = [];
|
||||
$storyids = [];
|
||||
$request = $this->request;
|
||||
$parsedBody = $request->getParsedBody();
|
||||
|
||||
if (!empty($parsedBody['downloads'])) {
|
||||
$ids = explode(',', $parsedBody['downloads']);
|
||||
foreach ($ids as $id) {
|
||||
$downloadids[] = intval($id);
|
||||
}
|
||||
$downloadids = implode(',', $downloadids);
|
||||
$downloads = $this->downloadRepository->findByUids($ids);
|
||||
}
|
||||
|
||||
if (!empty($parsedBody['stories'])) {
|
||||
$ids = explode(',', $parsedBody['stories']);
|
||||
foreach ($ids as $id) {
|
||||
$storyids[] = intval($id);
|
||||
}
|
||||
$storyids = implode(',', $storyids);
|
||||
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tt_content');
|
||||
$whereExpressions = [
|
||||
$queryBuilder->expr()->eq('colPos', $queryBuilder->createNamedParameter(0)),
|
||||
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(18)),
|
||||
$queryBuilder->expr()->in(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter(
|
||||
explode(',', $storyids),
|
||||
Connection::PARAM_INT_ARRAY
|
||||
)
|
||||
),
|
||||
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('mask_use_case')),
|
||||
$queryBuilder->expr()->eq('tx_mask_app_visible', $queryBuilder->createNamedParameter(1))
|
||||
];
|
||||
|
||||
$result = $queryBuilder->select('tt_content.*')
|
||||
->from('tt_content')
|
||||
->where(...$whereExpressions)
|
||||
->orderBy('tt_content.sorting')
|
||||
->executeQuery();
|
||||
$rows = $result->fetchAllAssociative();
|
||||
|
||||
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
|
||||
|
||||
foreach ($rows as $content) {
|
||||
$pdf_url = '';
|
||||
$filename = '';
|
||||
if (!empty($content['tx_mask_detail_pdf'])) {
|
||||
try {
|
||||
$filename = Environment::getProjectPath() . '/' . $content['tx_mask_detail_pdf'];
|
||||
$pdf_url = $this->uriBuilder->setTargetPageUid(1)->buildFrontendUri() .
|
||||
substr(str_replace(Environment::getProjectPath(), '', $filename), 1);
|
||||
} catch (\Exception $e) {
|
||||
// Handle exception
|
||||
}
|
||||
}
|
||||
$stories[$content['uid']] = [
|
||||
'uid' => $content['uid'],
|
||||
'header' => $content['header'],
|
||||
'pdf' => $filename,
|
||||
'detail_pdf' => $pdf_url
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($parsedBody['email']) &&
|
||||
filter_var($parsedBody['email'], FILTER_VALIDATE_EMAIL) &&
|
||||
(!empty($parsedBody['downloads']) || !empty($parsedBody['stories']))) {
|
||||
|
||||
$file = $this->createZip($parsedBody['downloads'] . $parsedBody['stories'], $downloads, $stories);
|
||||
if ($file !== false) {
|
||||
$email = GeneralUtility::makeInstance(FluidEmail::class);
|
||||
$email->from('downloads@vitec.com', 'VITEC')
|
||||
->to($parsedBody['email'])
|
||||
->subject('VITEC Datasheets download')
|
||||
->text('Thank you for your interest in VITEC products. Attached, please find your Datasheets ready for download.')
|
||||
->attachFromPath($file, 'Vitec.zip');
|
||||
|
||||
$this->mailer->send($email);
|
||||
|
||||
$redirect = $this->uriBuilder->setTargetPageUid(186)->setCreateAbsoluteUri(true)->buildFrontendUri();
|
||||
return $this->redirectToUri($redirect);
|
||||
}
|
||||
} elseif (!empty($parsedBody['downloads']) || !empty($parsedBody['stories'])) {
|
||||
$file = $this->createZip($parsedBody['downloads'] . $parsedBody['stories'], $downloads, $stories);
|
||||
if ($file !== false) {
|
||||
$response = $this->responseFactory->createResponse()
|
||||
->withHeader('Content-disposition', 'attachment; filename=Vitec.zip')
|
||||
->withHeader('Content-type', 'application/zip')
|
||||
->withHeader('Content-Length', (string)filesize($file));
|
||||
|
||||
$stream = new Stream(fopen($file, 'rb'));
|
||||
unlink($file);
|
||||
return $response->withBody($stream);
|
||||
}
|
||||
}
|
||||
|
||||
$this->view->assign('downloads', $downloads);
|
||||
$this->view->assign('downloadids', $downloadids);
|
||||
$this->view->assign('stories', $stories);
|
||||
$this->view->assign('storyids', $storyids);
|
||||
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Controller;
|
||||
|
||||
use Evomedien\Vitec\Domain\Repository\MarketRepository;
|
||||
use Evomedien\Vitec\Domain\Model\Market;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* MarketController
|
||||
*/
|
||||
class MarketController extends ActionController
|
||||
{
|
||||
/**
|
||||
* marketRepository
|
||||
*
|
||||
* @var MarketRepository
|
||||
*/
|
||||
protected $marketRepository;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param MarketRepository $marketRepository
|
||||
*/
|
||||
public function __construct(MarketRepository $marketRepository)
|
||||
{
|
||||
$this->marketRepository = $marketRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* action show
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function showAction(): ResponseInterface
|
||||
{
|
||||
$selectedMarketId = (int)$this->settings['market'];
|
||||
$market = null;
|
||||
if ($selectedMarketId) {
|
||||
$market = $this->marketRepository->findByUid($selectedMarketId);
|
||||
}
|
||||
|
||||
$this->view->assign('market', $market);
|
||||
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Controller;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use Evomedien\Vitec\Domain\Repository\ProductRepository;
|
||||
use Evomedien\Vitec\PageTitle\ProductPageTitleProvider;
|
||||
use TYPO3\CMS\Core\Routing\PageLinkBuilder;
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* ProductController
|
||||
*/
|
||||
class ProductController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* productRepository
|
||||
*
|
||||
* @var \Evomedien\Vitec\Domain\Repository\ProductRepository
|
||||
*/
|
||||
protected $productRepository;
|
||||
|
||||
/**
|
||||
* @var \Evomedien\Vitec\PageTitle\ProductPageTitleProvider
|
||||
*/
|
||||
protected $titleProvider;
|
||||
|
||||
|
||||
/**
|
||||
* @param \Evomedien\Vitec\Domain\Repository\ProductRepository $productRepository
|
||||
*/
|
||||
public function injectProductRepository(\Evomedien\Vitec\Domain\Repository\ProductRepository $productRepository)
|
||||
{
|
||||
$this->productRepository = $productRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the TitleProvider
|
||||
*
|
||||
* @param \Evomedien\Vitec\PageTitle\ProductPageTitleProvider $titleProvider
|
||||
*/
|
||||
public function injectTitleProvider(ProductPageTitleProvider $titleProvider): void
|
||||
{
|
||||
$this->titleProvider = $titleProvider;
|
||||
}
|
||||
/**
|
||||
* @var LinkService
|
||||
*/
|
||||
protected $linkService;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ProductRepository $productRepository
|
||||
* @param ProductPageTitleProvider $titleProvider
|
||||
* @param LinkService $linkService
|
||||
*/
|
||||
public function __construct(
|
||||
ProductRepository $productRepository,
|
||||
ProductPageTitleProvider $titleProvider,
|
||||
|
||||
) {
|
||||
$this->productRepository = $productRepository;
|
||||
$this->titleProvider = $titleProvider;
|
||||
|
||||
}
|
||||
/**
|
||||
* action index
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
public function indexAction(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* action list
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
public function listAction(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
// Extract and sanitize category UIDs from FlexForm
|
||||
$categoryUids = array_filter(
|
||||
array_map('intval', explode(',', (string)($this->settings['categories'] ?? '')))
|
||||
);
|
||||
|
||||
// Get filtered products based on categories and visibility flags
|
||||
$products = $this->productRepository->findFilteredProducts($categoryUids);
|
||||
|
||||
$this->view->assign('products', $products);
|
||||
|
||||
// Check if we're in headless mode (JSON request)
|
||||
$request = $this->request;
|
||||
$pageType = $request->getAttribute('routing')?->getPageType() ?? 0;
|
||||
|
||||
if ($pageType === 834) { // Headless JSON page type
|
||||
return $this->jsonResponse(json_encode([
|
||||
'products' => $this->serializeProducts($products)
|
||||
]));
|
||||
}
|
||||
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize products to array for JSON output
|
||||
*/
|
||||
protected function serializeProducts($products): array
|
||||
{
|
||||
$productsData = [];
|
||||
|
||||
foreach ($products as $product) {
|
||||
$categories = [];
|
||||
if ($product->getCategories()) {
|
||||
foreach ($product->getCategories() as $category) {
|
||||
$categories[] = [
|
||||
'uid' => $category->getUid(),
|
||||
'title' => $category->getTitle(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$image = null;
|
||||
if ($product->getProductimage() && $product->getProductimage()->count() > 0) {
|
||||
$imageObject = $product->getProductimage()->current();
|
||||
$image = [
|
||||
'uid' => $imageObject->getUid(),
|
||||
'url' => $imageObject->getOriginalResource()->getPublicUrl(),
|
||||
'title' => $imageObject->getTitle(),
|
||||
'alternative' => $imageObject->getAlternative(),
|
||||
'description' => $imageObject->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
$productsData[] = [
|
||||
'uid' => $product->getUid(),
|
||||
'title' => $product->getTitle(),
|
||||
'subtitle' => $product->getSubtitle(),
|
||||
'teaser' => $product->getTeaser(),
|
||||
'description' => $product->getDescription(),
|
||||
'slug' => $product->getSlug(),
|
||||
'categories' => $categories,
|
||||
'image' => $image,
|
||||
];
|
||||
}
|
||||
|
||||
return $productsData;
|
||||
}
|
||||
|
||||
/**
|
||||
* action show
|
||||
*
|
||||
* @param \Evomedien\Vitec\Domain\Model\Product $product
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
public function showAction(\Evomedien\Vitec\Domain\Model\Product $product): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
|
||||
$metaTagManager = GeneralUtility::makeInstance(MetaTagManagerRegistry::class)->getManagerForProperty('og:title');
|
||||
$metaTagManager->addProperty('og:title', ($product->getSeotitle()));
|
||||
$metaTagManager->addProperty('og:description', ($product->getDescription()));
|
||||
//$metaTagManager->addProperty('og:url', $this->request->getRequestUri());
|
||||
$metaTagManager->addProperty('og:type', 'product');
|
||||
|
||||
if ($product->getOgimage()) {
|
||||
$ogImageUrl = $product->getOgimage()->getOriginalResource()->getPublicUrl();
|
||||
$metaTagManager->addProperty('og:image', $ogImageUrl);
|
||||
}
|
||||
|
||||
// Access the layout setting from FlexForm
|
||||
$layout = $this->settings['layout'] ?? 0;
|
||||
|
||||
|
||||
// Pass the layout to the view
|
||||
$this->view->assign('layout', $layout);
|
||||
|
||||
$this->view->assign('product', $product);
|
||||
$this->titleProvider->setSeoTitle($product->getSeotitle());
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace Evomedien\Vitec\Controller;
|
||||
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
|
||||
class SimplecardController extends ActionController
|
||||
{
|
||||
public function listAction(): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Controller;
|
||||
|
||||
use Evomedien\Vitec\Domain\Repository\SolutionRepository;
|
||||
use Evomedien\Vitec\Domain\Model\Solution;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* SolutionController
|
||||
*/
|
||||
class SolutionController extends ActionController
|
||||
{
|
||||
/**
|
||||
* solutionRepository
|
||||
*
|
||||
* @var SolutionRepository
|
||||
*/
|
||||
protected $solutionRepository;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param SolutionRepository $solutionRepository
|
||||
*/
|
||||
public function __construct(SolutionRepository $solutionRepository)
|
||||
{
|
||||
$this->solutionRepository = $solutionRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* action show
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function showAction(): ResponseInterface
|
||||
{
|
||||
$selectedSolutionId = (int)$this->settings['solution'];
|
||||
$solution = null;
|
||||
if ($selectedSolutionId) {
|
||||
$solution = $this->solutionRepository->findByUid($selectedSolutionId);
|
||||
}
|
||||
|
||||
$this->view->assign('solution', $solution);
|
||||
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Controller;
|
||||
|
||||
use Evomedien\Vitec\Domain\Repository\UsecaseRepository;
|
||||
use Evomedien\Vitec\Domain\Model\Usecase;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* UsecaseController
|
||||
*/
|
||||
class UsecaseController extends ActionController
|
||||
{
|
||||
/**
|
||||
* usecaseRepository
|
||||
*
|
||||
* @var UsecaseRepository
|
||||
*/
|
||||
protected $usecaseRepository;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param UsecaseRepository $usecaseRepository
|
||||
*/
|
||||
public function __construct(UsecaseRepository $usecaseRepository)
|
||||
{
|
||||
$this->usecaseRepository = $usecaseRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* action show
|
||||
*
|
||||
* @param Usecase $usecase
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function showAction(): ResponseInterface
|
||||
{
|
||||
$selectedUsecaseId = (int)$this->settings['usecase']; // Die ID aus dem Flexform
|
||||
$usecase = null;
|
||||
if ($selectedUsecaseId) {
|
||||
$usecase = $this->usecaseRepository->findByUid($selectedUsecaseId);
|
||||
}
|
||||
|
||||
$this->view->assign('usecase', $usecase);
|
||||
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* action list
|
||||
*
|
||||
* @param Usecase $usecase
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function listAction(): ResponseInterface
|
||||
{
|
||||
$usecases = $this->usecaseRepository->findAllWithCategories();
|
||||
$this->view->assign('usecases', $usecases);
|
||||
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\DataProcessing;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
|
||||
/**
|
||||
* Collect container children grouped by colPos and emit a lean, transport-
|
||||
* ready structure. Each child is normalised to:
|
||||
* { id, type, colPos, sorting, appearance, data }
|
||||
* `data` only contains non-empty content-relevant fields.
|
||||
*
|
||||
* Exception-safe.
|
||||
*/
|
||||
final class ContainerChildrenProcessor implements DataProcessorInterface
|
||||
{
|
||||
/** Fields that go to the envelope (not to `data`). */
|
||||
private const ENVELOPE = [
|
||||
'uid', 'CType', 'colPos', 'sorting',
|
||||
'layout', 'frame_class', 'space_before_class', 'space_after_class',
|
||||
];
|
||||
|
||||
/** Technical / system / TCA-default fields — never sent to frontend. */
|
||||
private const SYSTEM_FIELDS = [
|
||||
// versioning / language / workspace / housekeeping
|
||||
'pid', 'sys_language_uid', 'l18n_parent', 'l18n_diffsource',
|
||||
'l10n_source', 'l10n_state', 'l10n_parent',
|
||||
't3_origuid', 'tx_impexp_origuid',
|
||||
'tx_container_parent',
|
||||
'tstamp', 'crdate', 'cruser_id',
|
||||
'hidden', 'deleted', 'starttime', 'endtime', 'fe_group',
|
||||
't3ver_oid', 't3ver_wsid', 't3ver_state', 't3ver_stage',
|
||||
't3ver_id', 't3ver_label', 't3ver_count', 't3ver_tstamp',
|
||||
'editlock', 'sorting_foreign', 'rowDescription',
|
||||
'spaceBefore', 'spaceAfter', // legacy
|
||||
// TCA defaults that TYPO3 always sets on every tt_content row,
|
||||
// regardless of CType — rarely relevant to the frontend:
|
||||
'imagecols', 'sectionIndex', 'linkToTop', 'recursive', 'date',
|
||||
'bullets_type', 'cols',
|
||||
'table_delimiter', 'table_enclosure', 'table_header_position',
|
||||
'table_tfoot', 'table_caption',
|
||||
'filelink_size', 'filelink_sorting', 'filelink_sorting_direction',
|
||||
'uploads_description', 'uploads_type',
|
||||
];
|
||||
|
||||
/** Fields whose 0/empty value is still meaningful. */
|
||||
private const KEEP_IF_ZERO = [
|
||||
'header_layout',
|
||||
];
|
||||
|
||||
public function process(
|
||||
ContentObjectRenderer $cObj,
|
||||
array $contentObjectConfiguration,
|
||||
array $processorConfiguration,
|
||||
array $processedData
|
||||
): array {
|
||||
$as = (string)($processorConfiguration['as'] ?? 'items');
|
||||
|
||||
try {
|
||||
$parentUid = (int)($cObj->data['uid'] ?? 0);
|
||||
if ($parentUid <= 0) {
|
||||
$processedData[$as] = [];
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
$pid = (int)($cObj->data['pid'] ?? 0);
|
||||
$sysLanguageUid = (int)($cObj->data['sys_language_uid'] ?? 0);
|
||||
|
||||
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tt_content');
|
||||
$rows = $qb
|
||||
->select('*')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$qb->expr()->eq('tx_container_parent', $qb->createNamedParameter($parentUid, Connection::PARAM_INT)),
|
||||
$qb->expr()->eq('pid', $qb->createNamedParameter($pid, Connection::PARAM_INT)),
|
||||
$qb->expr()->eq('sys_language_uid', $qb->createNamedParameter($sysLanguageUid, Connection::PARAM_INT))
|
||||
)
|
||||
->orderBy('colPos')
|
||||
->addOrderBy('sorting')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
$byColPos = [];
|
||||
foreach ($rows as $record) {
|
||||
$byColPos[(int)$record['colPos']][] = $this->normalise($record);
|
||||
}
|
||||
ksort($byColPos);
|
||||
|
||||
$items = [];
|
||||
foreach ($byColPos as $colPos => $contentElements) {
|
||||
$items[] = [
|
||||
'config' => ['colPos' => $colPos],
|
||||
'contentElements' => $contentElements,
|
||||
];
|
||||
}
|
||||
|
||||
$processedData[$as] = $items;
|
||||
} catch (\Throwable $e) {
|
||||
$processedData[$as] = [];
|
||||
}
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
private function normalise(array $record): array
|
||||
{
|
||||
$data = [];
|
||||
foreach ($record as $field => $value) {
|
||||
if (in_array($field, self::ENVELOPE, true)) {
|
||||
continue;
|
||||
}
|
||||
if (in_array($field, self::SYSTEM_FIELDS, true)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->isEmpty($value) && !in_array($field, self::KEEP_IF_ZERO, true)) {
|
||||
continue;
|
||||
}
|
||||
$data[$field] = $this->castValue($field, $value);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int)$record['uid'],
|
||||
'type' => (string)$record['CType'],
|
||||
'colPos' => (int)$record['colPos'],
|
||||
'sorting' => (int)($record['sorting'] ?? 0),
|
||||
'appearance' => [
|
||||
'layout' => (string)($record['layout'] ?? ''),
|
||||
'frameClass' => (string)($record['frame_class'] ?? 'default'),
|
||||
'spaceBefore' => (string)($record['space_before_class'] ?? ''),
|
||||
'spaceAfter' => (string)($record['space_after_class'] ?? ''),
|
||||
],
|
||||
'data' => (object)$data,
|
||||
];
|
||||
}
|
||||
|
||||
private function isEmpty(mixed $value): bool
|
||||
{
|
||||
return $value === null
|
||||
|| $value === ''
|
||||
|| $value === 0
|
||||
|| $value === '0';
|
||||
}
|
||||
|
||||
private function castValue(string $field, mixed $value): mixed
|
||||
{
|
||||
if (in_array($field, ['header_layout'], true)) {
|
||||
return (int)$value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\DataProcessing;
|
||||
|
||||
use Evomedien\Vitec\Domain\Model\Dto\ProductListContentElement;
|
||||
use Evomedien\Vitec\Domain\Repository\ProductRepository;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
|
||||
/**
|
||||
* Data processor for vitec_productlist content element
|
||||
*/
|
||||
class ProductListProcessor implements DataProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected readonly ProductRepository $productRepository,
|
||||
protected readonly FlexFormService $flexFormService
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(
|
||||
ContentObjectRenderer $cObj,
|
||||
array $contentObjectConfiguration,
|
||||
array $processorConfiguration,
|
||||
array $processedData
|
||||
): array {
|
||||
// Only process if this is a vitec_productlist
|
||||
if (($processedData['data']['list_type'] ?? '') !== 'vitec_productlist') {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
// Get FlexForm settings
|
||||
$flexFormData = $this->flexFormService->convertFlexFormContentToArray(
|
||||
$processedData['data']['pi_flexform'] ?? ''
|
||||
);
|
||||
|
||||
$settings = $flexFormData['settings'] ?? [];
|
||||
|
||||
// Extract and sanitize category UIDs from FlexForm
|
||||
$categoryUids = array_filter(
|
||||
array_map('intval', explode(',', (string)($settings['categories'] ?? '')))
|
||||
);
|
||||
|
||||
// Get filtered products based on categories
|
||||
$products = $this->productRepository->findFilteredProducts($categoryUids);
|
||||
|
||||
// Serialize products directly
|
||||
$productsData = [];
|
||||
foreach ($products as $product) {
|
||||
$categories = [];
|
||||
if ($product->getCategories()) {
|
||||
foreach ($product->getCategories() as $category) {
|
||||
$categories[] = [
|
||||
'uid' => $category->getUid(),
|
||||
'title' => $category->getTitle(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$image = null;
|
||||
if ($product->getProductimage() && $product->getProductimage()->count() > 0) {
|
||||
$imageObject = $product->getProductimage()->current();
|
||||
$image = [
|
||||
'uid' => $imageObject->getUid(),
|
||||
'url' => $imageObject->getOriginalResource()->getPublicUrl(),
|
||||
'title' => $imageObject->getTitle(),
|
||||
'alternative' => $imageObject->getAlternative(),
|
||||
'description' => $imageObject->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
$productsData[] = [
|
||||
'uid' => $product->getUid(),
|
||||
'title' => $product->getTitle(),
|
||||
'subtitle' => $product->getSubtitle(),
|
||||
'teaser' => $product->getTeaser(),
|
||||
'description' => $product->getDescription(),
|
||||
'slug' => $product->getSlug(),
|
||||
'categories' => $categories,
|
||||
'image' => $image,
|
||||
];
|
||||
}
|
||||
|
||||
$processedData['products'] = $productsData;
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\DataProcessing;
|
||||
|
||||
use Evomedien\Vitec\Domain\Repository\ProductRepository;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
||||
|
||||
/**
|
||||
* Simple and direct data processor for vitec_productlist
|
||||
*/
|
||||
class VitecProductProcessor implements DataProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected readonly ProductRepository $productRepository,
|
||||
protected readonly FlexFormService $flexFormService
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(
|
||||
ContentObjectRenderer $cObj,
|
||||
array $contentObjectConfiguration,
|
||||
array $processorConfiguration,
|
||||
array $processedData
|
||||
): array {
|
||||
// Only process vitec_productlist
|
||||
if (($processedData['data']['list_type'] ?? '') !== 'vitec_productlist') {
|
||||
return $processedData;
|
||||
}
|
||||
|
||||
// Get FlexForm settings
|
||||
$flexFormData = $this->flexFormService->convertFlexFormContentToArray(
|
||||
$processedData['data']['pi_flexform'] ?? ''
|
||||
);
|
||||
|
||||
$settings = $flexFormData['settings'] ?? [];
|
||||
|
||||
// Extract category UIDs
|
||||
$categoryUids = array_filter(
|
||||
array_map('intval', explode(',', (string)($settings['categories'] ?? '')))
|
||||
);
|
||||
|
||||
// Get products
|
||||
$products = $this->productRepository->findFilteredProducts($categoryUids);
|
||||
|
||||
// Serialize products
|
||||
$productsData = [];
|
||||
foreach ($products as $product) {
|
||||
$productsData[] = [
|
||||
'uid' => $product->getUid(),
|
||||
'title' => $product->getTitle(),
|
||||
'slug' => $product->getSlug(),
|
||||
];
|
||||
}
|
||||
|
||||
// Add to processedData at content level
|
||||
if (!isset($processedData['content'])) {
|
||||
$processedData['content'] = [];
|
||||
}
|
||||
$processedData['content']['products'] = $productsData;
|
||||
|
||||
return $processedData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Evomedien\Vitec\Domain\Model;
|
||||
|
||||
use TYPO3\CMS\Extbase\Domain\Model\Category as ExtbaseCategory;
|
||||
|
||||
class Category extends ExtbaseCategory
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '';
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClass(): string
|
||||
{
|
||||
return $this->class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
*/
|
||||
public function setClass(string $class): void
|
||||
{
|
||||
$this->class = $class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Evomedien\Vitec\Domain\Model;
|
||||
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
|
||||
|
||||
class Download extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $slug = '';
|
||||
|
||||
|
||||
/* ----------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSlug(): string
|
||||
{
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $slug
|
||||
*/
|
||||
public function setSlug(string $slug): void
|
||||
{
|
||||
$this->slug = $slug;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------*/
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Domain\Model\Dto;
|
||||
|
||||
/**
|
||||
* DTO for Product List Content Element in JSON output
|
||||
*/
|
||||
class ProductListContentElement implements \JsonSerializable
|
||||
{
|
||||
protected array $products = [];
|
||||
protected array $settings = [];
|
||||
|
||||
public function setProducts(array $products): void
|
||||
{
|
||||
$this->products = $products;
|
||||
}
|
||||
|
||||
public function setSettings(array $settings): void
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$productsData = [];
|
||||
|
||||
foreach ($this->products as $product) {
|
||||
$productsData[] = [
|
||||
'uid' => $product->getUid(),
|
||||
'title' => $product->getTitle(),
|
||||
'subtitle' => $product->getSubtitle(),
|
||||
'teaser' => $product->getTeaser(),
|
||||
'description' => $product->getDescription(),
|
||||
'slug' => $product->getSlug(),
|
||||
'categories' => $this->serializeCategories($product->getCategories()),
|
||||
'image' => $this->serializeImage($product->getProductimage()),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'products' => $productsData,
|
||||
'settings' => $this->settings,
|
||||
];
|
||||
}
|
||||
|
||||
protected function serializeCategories($categories): array
|
||||
{
|
||||
$categoriesData = [];
|
||||
|
||||
if ($categories) {
|
||||
foreach ($categories as $category) {
|
||||
$categoriesData[] = [
|
||||
'uid' => $category->getUid(),
|
||||
'title' => $category->getTitle(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $categoriesData;
|
||||
}
|
||||
|
||||
protected function serializeImage($images): ?array
|
||||
{
|
||||
if (!$images || $images->count() === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$imageObject = $images->current();
|
||||
|
||||
return [
|
||||
'uid' => $imageObject->getUid(),
|
||||
'url' => $imageObject->getOriginalResource()->getPublicUrl(),
|
||||
'title' => $imageObject->getTitle(),
|
||||
'alternative' => $imageObject->getAlternative(),
|
||||
'description' => $imageObject->getDescription(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace Evomedien\Vitec\Domain\Model;
|
||||
|
||||
use TYPO3\CMS\Extbase\Domain\Model\Category;
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
|
||||
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
|
||||
|
||||
class Market extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $subtitle = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $teaser = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Domain\Model\FileReference
|
||||
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
|
||||
*/
|
||||
protected $image = null;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\Category>
|
||||
* @TYPO3\CMS\Extbase\Annotation\ORM\Lazy
|
||||
*/
|
||||
protected $categories;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->categories = new ObjectStorage();
|
||||
}
|
||||
|
||||
/* ---------------------------------------------- */
|
||||
/* Getter und Setter */
|
||||
/* ---------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSubtitle(): string
|
||||
{
|
||||
return $this->subtitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $subtitle
|
||||
*/
|
||||
public function setSubtitle(string $subtitle): void
|
||||
{
|
||||
$this->subtitle = $subtitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTeaser(): string
|
||||
{
|
||||
return $this->teaser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $teaser
|
||||
*/
|
||||
public function setTeaser(string $teaser): void
|
||||
{
|
||||
$this->teaser = $teaser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $description
|
||||
*/
|
||||
public function setDescription(string $description): void
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Extbase\Domain\Model\FileReference|null
|
||||
*/
|
||||
public function getImage()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $image
|
||||
*/
|
||||
public function setImage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $image): void
|
||||
{
|
||||
$this->image = $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Category $category
|
||||
*/
|
||||
public function addCategory(Category $category): void
|
||||
{
|
||||
$this->categories->attach($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Category $categoryToRemove
|
||||
*/
|
||||
public function removeCategory(Category $categoryToRemove): void
|
||||
{
|
||||
$this->categories->detach($categoryToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectStorage<Category>
|
||||
*/
|
||||
public function getCategories(): ObjectStorage
|
||||
{
|
||||
return $this->categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectStorage<Category> $categories
|
||||
*/
|
||||
public function setCategories(ObjectStorage $categories): void
|
||||
{
|
||||
$this->categories = $categories;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace Evomedien\Vitec\Domain\Model;
|
||||
|
||||
use TYPO3\CMS\Extbase\Domain\Model\Category;
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
|
||||
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
|
||||
|
||||
class Solution extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $subtitle = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $teaser = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Domain\Model\FileReference
|
||||
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
|
||||
*/
|
||||
protected $image = null;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\Category>
|
||||
* @TYPO3\CMS\Extbase\Annotation\ORM\Lazy
|
||||
*/
|
||||
protected $categories;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->categories = new ObjectStorage();
|
||||
}
|
||||
|
||||
/* ---------------------------------------------- */
|
||||
/* Getter und Setter */
|
||||
/* ---------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSubtitle(): string
|
||||
{
|
||||
return $this->subtitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $subtitle
|
||||
*/
|
||||
public function setSubtitle(string $subtitle): void
|
||||
{
|
||||
$this->subtitle = $subtitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTeaser(): string
|
||||
{
|
||||
return $this->teaser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $teaser
|
||||
*/
|
||||
public function setTeaser(string $teaser): void
|
||||
{
|
||||
$this->teaser = $teaser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $description
|
||||
*/
|
||||
public function setDescription(string $description): void
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Extbase\Domain\Model\FileReference|null
|
||||
*/
|
||||
public function getImage()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $image
|
||||
*/
|
||||
public function setImage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $image): void
|
||||
{
|
||||
$this->image = $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Category $category
|
||||
*/
|
||||
public function addCategory(Category $category): void
|
||||
{
|
||||
$this->categories->attach($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Category $categoryToRemove
|
||||
*/
|
||||
public function removeCategory(Category $categoryToRemove): void
|
||||
{
|
||||
$this->categories->detach($categoryToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectStorage<Category>
|
||||
*/
|
||||
public function getCategories(): ObjectStorage
|
||||
{
|
||||
return $this->categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectStorage<Category> $categories
|
||||
*/
|
||||
public function setCategories(ObjectStorage $categories): void
|
||||
{
|
||||
$this->categories = $categories;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
namespace Evomedien\Vitec\Domain\Model;
|
||||
use TYPO3\CMS\Extbase\Domain\Model\Category;
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
|
||||
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
|
||||
|
||||
class Usecase extends AbstractEntity
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $slug = '';
|
||||
|
||||
/**
|
||||
* subtitle
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $subtitle = '';
|
||||
|
||||
/**
|
||||
* teaser
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $teaser = '';
|
||||
|
||||
/**
|
||||
* hideonapp
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hideonapp = false;
|
||||
|
||||
/**
|
||||
* hideonwebsite
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hideonwebsite = false;
|
||||
|
||||
|
||||
/**
|
||||
* description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $singlepid = '';
|
||||
|
||||
/**
|
||||
* caseimage
|
||||
*
|
||||
* @var \TYPO3\CMS\Extbase\Domain\Model\FileReference
|
||||
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade
|
||||
*/
|
||||
protected $caseimage = '';
|
||||
|
||||
/**
|
||||
* logoimage
|
||||
*
|
||||
* @var \TYPO3\CMS\Extbase\Domain\Model\FileReference
|
||||
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade
|
||||
*/
|
||||
protected $logoimage = null;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\Category>
|
||||
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
|
||||
*/
|
||||
protected $categories;
|
||||
|
||||
/* ---------------------------------------------- */
|
||||
/* Getter und Setter */
|
||||
/* ---------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSlug(): string
|
||||
{
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $slug
|
||||
*/
|
||||
public function setSlug(string $slug): void
|
||||
{
|
||||
$this->slug = $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSinglepid(): string
|
||||
{
|
||||
return $this->singlepid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $singlepid
|
||||
*/
|
||||
public function setSinglepid(string $singlepid): void
|
||||
{
|
||||
$this->singlepid = $singlepid;
|
||||
}
|
||||
/**
|
||||
* 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 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 description
|
||||
*
|
||||
* @return string $description
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description
|
||||
*
|
||||
* @param string $description
|
||||
* @return void
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the caseimage
|
||||
*
|
||||
* @return \TYPO3\CMS\Extbase\Domain\Model\FileReference $caseimage
|
||||
*/
|
||||
public function getCaseimage()
|
||||
{
|
||||
return $this->caseimage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the caseimage
|
||||
*
|
||||
* @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $caseimage
|
||||
* @return void
|
||||
*/
|
||||
public function setCaseimage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $caseimage)
|
||||
{
|
||||
$this->caseimage = $caseimage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the logoimage
|
||||
*
|
||||
* @return \TYPO3\CMS\Extbase\Domain\Model\FileReference $logoimage
|
||||
*/
|
||||
public function getLogoimage()
|
||||
{
|
||||
return $this->logoimage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the logoimage
|
||||
*
|
||||
* @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $logoimage
|
||||
* @return void
|
||||
*/
|
||||
public function setLogoimage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $logoimage)
|
||||
{
|
||||
$this->logoimage = $logoimage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Domain\Repository;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* The repository for Downloads
|
||||
*/
|
||||
class DownloadRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
|
||||
{
|
||||
public function findByUids(array $uids)
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
return $query->matching(
|
||||
$query->in('uid', $uids) // Use 'uid' instead of 'uids'
|
||||
)->execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Domain\Repository;
|
||||
|
||||
use TYPO3\CMS\Extbase\Persistence\Repository;
|
||||
|
||||
class MarketRepository extends Repository
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
namespace Evomedien\Vitec\Domain\Repository;
|
||||
|
||||
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
|
||||
use TYPO3\CMS\Extbase\Persistence\Repository;
|
||||
|
||||
class ProductRepository extends Repository
|
||||
{
|
||||
/**
|
||||
* Find products with visibility filters and optional category filtering
|
||||
*
|
||||
* @param array $categoryUids Optional category UIDs to filter by
|
||||
* @param bool $respectVisibility Whether to apply visibility filters
|
||||
* @return QueryResultInterface
|
||||
*/
|
||||
public function findFilteredProducts(array $categoryUids = [], bool $respectVisibility = true): QueryResultInterface
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$constraints = [];
|
||||
|
||||
// Add visibility constraints if requested
|
||||
if ($respectVisibility) {
|
||||
$constraints[] = $query->equals('legacy', 0);
|
||||
$constraints[] = $query->equals('supportproduct', 0);
|
||||
$constraints[] = $query->equals('hideonwebsite', 0);
|
||||
$constraints[] = $query->equals('hideonproducts', 0);
|
||||
$constraints[] = $query->equals('subproduct', 0);
|
||||
}
|
||||
|
||||
// Add category filtering if categories are provided
|
||||
$categoryUids = array_filter(array_map('intval', $categoryUids));
|
||||
if (!empty($categoryUids)) {
|
||||
// For TYPO3 13.4, use contains() for MM relations to sys_category
|
||||
$categoryConstraints = [];
|
||||
foreach ($categoryUids as $categoryUid) {
|
||||
$categoryConstraints[] = $query->contains('categories', $categoryUid);
|
||||
}
|
||||
$constraints[] = $query->logicalOr(...$categoryConstraints);
|
||||
}
|
||||
|
||||
// Apply constraints if any exist
|
||||
if (!empty($constraints)) {
|
||||
$query->matching($query->logicalAnd(...$constraints));
|
||||
}
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Find all visible products (convenience method)
|
||||
*/
|
||||
public function findAllVisible(): QueryResultInterface
|
||||
{
|
||||
return $this->findFilteredProducts([], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find products by categories only (no visibility filtering)
|
||||
*/
|
||||
public function findByCategoriesOnly(array $categoryUids): QueryResultInterface
|
||||
{
|
||||
return $this->findFilteredProducts($categoryUids, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Domain\Repository;
|
||||
|
||||
use TYPO3\CMS\Extbase\Persistence\Repository;
|
||||
|
||||
class SolutionRepository extends Repository
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Evomedien\Vitec\Domain\Repository;
|
||||
|
||||
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* The repository for Usecase
|
||||
*/
|
||||
class UsecaseRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
|
||||
{
|
||||
|
||||
public function findAllWithCategories(): QueryResultInterface
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$query->getQuerySettings()->setRespectStoragePage(false); // Fetch from all storage pages
|
||||
return $query->execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\EventListener;
|
||||
|
||||
use Evomedien\Vitec\View\BackendLayoutDataProvider;
|
||||
use TYPO3\CMS\Backend\Controller\Event\ModifyPageLayoutContentEvent;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Attribute\AsEventListener;
|
||||
|
||||
/**
|
||||
* Adds an info banner at the top of the page module showing the active
|
||||
* VITEC frontend layout name. Helps editors immediately see which layout
|
||||
* the current page is using.
|
||||
*/
|
||||
#[AsEventListener(identifier: 'vitec/show-frontend-layout-banner')]
|
||||
final class ShowFrontendLayoutBanner
|
||||
{
|
||||
public function __invoke(ModifyPageLayoutContentEvent $event): void
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
$pageId = (int)($request->getQueryParams()['id'] ?? 0);
|
||||
if ($pageId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$page = BackendUtility::getRecord('pages', $pageId, 'layout');
|
||||
if (!is_array($page)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$layoutValue = (int)($page['layout'] ?? 0);
|
||||
$title = BackendLayoutDataProvider::LAYOUT_MAP[$layoutValue]['title'] ?? null;
|
||||
if ($title === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$banner = sprintf(
|
||||
'<div class="callout callout-info" style="margin-bottom:1rem">'
|
||||
. '<div class="callout-content">'
|
||||
. '<div class="callout-title">Frontend Layout</div>'
|
||||
. '<div class="callout-body"><strong>%s</strong> '
|
||||
. '<span class="text-body-secondary">(value: %d)</span></div>'
|
||||
. '</div>'
|
||||
. '</div>',
|
||||
htmlspecialchars($title, ENT_QUOTES, 'UTF-8'),
|
||||
$layoutValue
|
||||
);
|
||||
|
||||
$event->addHeaderContent($banner);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Hook;
|
||||
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
|
||||
/**
|
||||
* Auto-syncs pages.backend_layout = "vitec_<N>" whenever pages.layout is saved.
|
||||
* Also syncs backend_layout_next_level so subpages inherit the same layout
|
||||
* (until they pick their own frontend layout).
|
||||
*
|
||||
* Hooked via $TYPO3_CONF_VARS[SC_OPTIONS][t3lib/class.t3lib_tcemain.php][processDatamapClass].
|
||||
*/
|
||||
final class SyncBackendLayoutHook
|
||||
{
|
||||
public function processDatamap_postProcessFieldArray(
|
||||
string $status,
|
||||
string $table,
|
||||
$id,
|
||||
array &$fieldArray,
|
||||
DataHandler $pObj
|
||||
): void {
|
||||
if ($table !== 'pages') {
|
||||
return;
|
||||
}
|
||||
if (!array_key_exists('layout', $fieldArray)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$layoutValue = (int)$fieldArray['layout'];
|
||||
$beIdentifier = 'vitec__' . $layoutValue;
|
||||
|
||||
$fieldArray['backend_layout'] = $beIdentifier;
|
||||
$fieldArray['backend_layout_next_level'] = $beIdentifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\PageTitle;
|
||||
|
||||
use TYPO3\CMS\Core\PageTitle\AbstractPageTitleProvider;
|
||||
|
||||
final class ProductPageTitleProvider extends AbstractPageTitleProvider
|
||||
{
|
||||
private string $seotitle = '';
|
||||
|
||||
public function setSeoTitle(string $seotitle): void
|
||||
{
|
||||
$this->seotitle = $seotitle;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->seotitle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\Preview;
|
||||
|
||||
use TYPO3\CMS\Backend\Preview\StandardContentPreviewRenderer;
|
||||
use TYPO3\CMS\Backend\View\BackendLayout\Grid\GridColumnItem;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
use Evomedien\Vitec\Domain\Repository\UsecaseRepository;
|
||||
|
||||
/**
|
||||
* Preview class for the Usecaseshow plugin
|
||||
*/
|
||||
class UsecaseshowPluginPreview extends StandardContentPreviewRenderer
|
||||
{
|
||||
/**
|
||||
* @param GridColumnItem $item
|
||||
* @return string
|
||||
*/
|
||||
public function renderPageModulePreviewContent(GridColumnItem $item): string
|
||||
{
|
||||
$record = $item->getRecord();
|
||||
|
||||
$view = GeneralUtility::makeInstance(StandaloneView::class);
|
||||
$view->setTemplatePathAndFilename('EXT:vitec/Resources/Private/Templates/Preview/Usecaseshow.html');
|
||||
|
||||
// Parse FlexForm data
|
||||
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
|
||||
$flexFormData = $flexFormService->convertFlexFormContentToArray($record['pi_flexform'] ?? '');
|
||||
|
||||
// Get usecase data if selected
|
||||
$usecaseData = null;
|
||||
$usecaseUid = (int)($flexFormData['settings']['usecase'] ?? 0);
|
||||
|
||||
if ($usecaseUid > 0) {
|
||||
$usecaseRepository = GeneralUtility::makeInstance(UsecaseRepository::class);
|
||||
$usecase = $usecaseRepository->findByUid($usecaseUid);
|
||||
if ($usecase) {
|
||||
$usecaseData = [
|
||||
'uid' => $usecase->getUid(),
|
||||
'title' => $usecase->getTitle(),
|
||||
'subtitle' => $usecase->getSubtitle(),
|
||||
'description' => $usecase->getDescription()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Get layout setting
|
||||
$layout = $flexFormData['settings']['layout'] ?? 'default';
|
||||
|
||||
// Assign variables to view
|
||||
$view->assignMultiple([
|
||||
'record' => $record,
|
||||
'flexFormData' => $flexFormData,
|
||||
'usecase' => $usecaseData,
|
||||
'layout' => $layout,
|
||||
'pluginName' => 'Usecaseshow'
|
||||
]);
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GridColumnItem $item
|
||||
* @return string
|
||||
*/
|
||||
public function renderPageModulePreviewHeader(GridColumnItem $item): string
|
||||
{
|
||||
$record = $item->getRecord();
|
||||
|
||||
// Parse FlexForm to get usecase info for header
|
||||
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
|
||||
$flexFormData = $flexFormService->convertFlexFormContentToArray($record['pi_flexform'] ?? '');
|
||||
|
||||
$usecaseUid = (int)($flexFormData['settings']['usecase'] ?? 0);
|
||||
|
||||
if ($usecaseUid > 0) {
|
||||
return '<strong>🎯 Usecase Show Plugin</strong> <small>(Usecase ID: ' . $usecaseUid . ')</small>';
|
||||
}
|
||||
|
||||
return '<strong>🎯 Usecase Show Plugin</strong> <small style="color: orange;">(No usecase selected)</small>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
<?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
|
||||
*/
|
||||
class ProductListJsonRenderer
|
||||
{
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
// IMPORTANT: Headless creates new cObj contexts when rendering JSON fields,
|
||||
// losing the current content element context. Also, Extbase repositories don't work
|
||||
// in UserFunc context because the full Extbase framework isn't bootstrapped.
|
||||
//
|
||||
// Solution: Query tt_content directly to find the plugin configuration,
|
||||
// then use direct database queries for products instead of Extbase repositories.
|
||||
|
||||
// Get current page UID
|
||||
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
|
||||
|
||||
// Query tt_content for vitec_productlist on this page
|
||||
$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)) {
|
||||
return json_encode(['debug' => 'No vitec_productlist on page ' . $pageId]);
|
||||
}
|
||||
|
||||
// Take the first one (there should typically be only one)
|
||||
$contentElement = $contentElements[0];
|
||||
|
||||
// Parse FlexForm
|
||||
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
|
||||
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
|
||||
$settings = $flexFormData['settings'] ?? [];
|
||||
|
||||
// Extract category UIDs and debug setting
|
||||
$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
|
||||
// Use direct database query instead
|
||||
$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)
|
||||
);
|
||||
|
||||
// Add category filter if specified and allProducts is not enabled
|
||||
if (!empty($categoryUids) && !$allProducts) {
|
||||
// Join with sys_category_record_mm to filter by categories
|
||||
$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();
|
||||
|
||||
// Serialize products (they're already associative arrays from the query)
|
||||
$productsData = [];
|
||||
foreach ($products as $product) {
|
||||
$productsData[] = $this->serializeProduct($product);
|
||||
}
|
||||
|
||||
// If debug mode is enabled, return object with products and debug info
|
||||
if ($debugMode) {
|
||||
return json_encode([
|
||||
'products' => $productsData,
|
||||
'debug' => [
|
||||
'pageId' => $pageId,
|
||||
'categoryUids' => $categoryUids,
|
||||
'productCount' => count($productsData),
|
||||
'settings' => $settings
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// Otherwise return products array directly (backward compatible)
|
||||
return json_encode($productsData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a single product DB row to the full headless JSON structure.
|
||||
*
|
||||
* Includes every field declared on the Product domain model
|
||||
* (Evomedien\Vitec\Domain\Model\Product) plus all fully resolved
|
||||
* relations (categories, productimage, downloads, ogimage, relatedprodukt).
|
||||
*
|
||||
* DB-only columns that are NOT part of the domain model
|
||||
* (cta, links, sorting1-5, key1-3, apptext1-3, productlayout, image,
|
||||
* relatedimage, system fields) are intentionally omitted.
|
||||
*
|
||||
* @param array<string,mixed> $product Associative DB row of tx_vitec_domain_model_product
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
protected function serializeProduct(array $product): array
|
||||
{
|
||||
$uid = (int)$product['uid'];
|
||||
|
||||
return [
|
||||
// --- identifier ---
|
||||
'uid' => $uid,
|
||||
|
||||
// --- scalar string fields (Product domain model) ---
|
||||
'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'] ?? ''),
|
||||
|
||||
// --- boolean flags (Product domain model) ---
|
||||
'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),
|
||||
|
||||
// --- convenience link (kept for backward compatibility) ---
|
||||
'link' => '/product/' . (string)($product['slug'] ?? ''),
|
||||
|
||||
// --- fully resolved relations ---
|
||||
'categories' => $this->getProductCategories($uid),
|
||||
'images' => $this->getProductImages($uid),
|
||||
'downloads' => $this->getProductDownloads($uid),
|
||||
'ogimage' => $this->getProductOgImage($uid),
|
||||
'relatedprodukt' => $this->getRelatedProducts($uid),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product images from FAL (sys_file_reference)
|
||||
* Processes images through ImageService and generates srcset for responsive images
|
||||
*/
|
||||
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 {
|
||||
// Get FAL FileReference object
|
||||
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
|
||||
|
||||
// Define image sizes for srcset
|
||||
$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'
|
||||
];
|
||||
}
|
||||
|
||||
// Get original/default image
|
||||
$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) {
|
||||
// Skip images that can't be processed
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the single Open Graph image (ogimage) for a product, or null.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories for a product (resolved sys_category records).
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all downloads for a product (resolved via tx_vitec_product_download_mm).
|
||||
*/
|
||||
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;
|
||||
|
||||
// Get file information from FAL if file reference exists
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get related products (resolved via tx_vitec_product_related_mm).
|
||||
*
|
||||
* Returns a shallow representation (no nested relations) to avoid
|
||||
* infinite recursion between mutually related products.
|
||||
*/
|
||||
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'];
|
||||
$images = $this->getProductImages($relUid);
|
||||
|
||||
$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' => $images,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
<?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
|
||||
*/
|
||||
class ProductListJsonRenderer
|
||||
{
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
// IMPORTANT: Headless creates new cObj contexts when rendering JSON fields,
|
||||
// losing the current content element context. Also, Extbase repositories don't work
|
||||
// in UserFunc context because the full Extbase framework isn't bootstrapped.
|
||||
//
|
||||
// Solution: Query tt_content directly to find the plugin configuration,
|
||||
// then use direct database queries for products instead of Extbase repositories.
|
||||
|
||||
// Get current page UID
|
||||
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
|
||||
|
||||
// Query tt_content for vitec_productlist on this page
|
||||
$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)) {
|
||||
return json_encode(['debug' => 'No vitec_productlist on page ' . $pageId]);
|
||||
}
|
||||
|
||||
// Take the first one (there should typically be only one)
|
||||
$contentElement = $contentElements[0];
|
||||
|
||||
// Parse FlexForm
|
||||
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
|
||||
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
|
||||
$settings = $flexFormData['settings'] ?? [];
|
||||
|
||||
// Extract category UIDs and debug setting
|
||||
$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
|
||||
// Use direct database query instead
|
||||
$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)
|
||||
);
|
||||
|
||||
// Add category filter if specified and allProducts is not enabled
|
||||
if (!empty($categoryUids) && !$allProducts) {
|
||||
// Join with sys_category_record_mm to filter by categories
|
||||
$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();
|
||||
|
||||
// Serialize products (they're already associative arrays from the query)
|
||||
$productsData = [];
|
||||
foreach ($products as $product) {
|
||||
// Get product images (FAL references)
|
||||
$images = $this->getProductImages((int)$product['uid']);
|
||||
|
||||
$productsData[] = [
|
||||
'uid' => (int)$product['uid'],
|
||||
'title' => $product['title'] ?? '',
|
||||
'subtitle' => $product['subtitle'] ?? '',
|
||||
'slug' => $product['slug'] ?? '',
|
||||
'teaser' => $product['teaser'] ?? '',
|
||||
'description' => $product['description'] ?? '',
|
||||
'link' => '/product/' . ($product['slug'] ?? ''),
|
||||
'images' => $images,
|
||||
// Add more fields as needed
|
||||
];
|
||||
}
|
||||
|
||||
// If debug mode is enabled, return object with products and debug info
|
||||
if ($debugMode) {
|
||||
return json_encode([
|
||||
'products' => $productsData,
|
||||
'debug' => [
|
||||
'pageId' => $pageId,
|
||||
'categoryUids' => $categoryUids,
|
||||
'productCount' => count($productsData),
|
||||
'settings' => $settings
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// Otherwise return products array directly (backward compatible)
|
||||
return json_encode($productsData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product images from FAL (sys_file_reference)
|
||||
* Processes images through ImageService and generates srcset for responsive images
|
||||
*/
|
||||
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 {
|
||||
// Get FAL FileReference object
|
||||
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
|
||||
|
||||
// Define image sizes for srcset
|
||||
$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'
|
||||
];
|
||||
}
|
||||
|
||||
// Get original/default image
|
||||
$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) {
|
||||
// Skip images that can't be processed
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
<?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 single product data as JSON for headless output
|
||||
*/
|
||||
class ProductShowJsonRenderer
|
||||
{
|
||||
public function render(string $content, array $conf): string
|
||||
{
|
||||
$pageId = (int)$GLOBALS['TSFE']->id;
|
||||
|
||||
// DEBUG: Log that the UserFunc is being called
|
||||
$debugInfo = [
|
||||
'userFuncCalled' => true,
|
||||
'pageId' => $pageId,
|
||||
'requestUri' => $_SERVER['REQUEST_URI'] ?? 'unknown',
|
||||
];
|
||||
|
||||
// Query tt_content for vitec_productshow on this page
|
||||
$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();
|
||||
|
||||
$debugInfo['contentElementsFound'] = count($contentElements);
|
||||
|
||||
if (empty($contentElements)) {
|
||||
$debugInfo['error'] = 'No vitec_productshow on page';
|
||||
return json_encode(['debug' => $debugInfo]);
|
||||
}
|
||||
|
||||
// Take the first one
|
||||
$contentElement = $contentElements[0];
|
||||
|
||||
// Parse FlexForm
|
||||
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
|
||||
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
|
||||
$settings = $flexFormData['settings'] ?? [];
|
||||
|
||||
// Get product UID from FlexForm or route parameter
|
||||
$productUid = (int)($settings['product'] ?? 0);
|
||||
$layout = (int)($settings['layout'] ?? 0);
|
||||
$debugMode = (bool)($settings['debug'] ?? false);
|
||||
|
||||
// If no product selected in FlexForm, try to get from route parameter
|
||||
if (!$productUid) {
|
||||
// Get the product parameter from GET request
|
||||
$routeParams = $GLOBALS['TYPO3_REQUEST']->getQueryParams();
|
||||
$productParam = $routeParams['tx_vitec_productshow']['product'] ?? null;
|
||||
|
||||
if ($productParam) {
|
||||
// If it's a slug, resolve it to UID
|
||||
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 json_encode([
|
||||
'error' => 'No product selected or found',
|
||||
'debug' => [
|
||||
'settings' => $settings,
|
||||
'routeParams' => $routeParams ?? [],
|
||||
'allQueryParams' => $GLOBALS['TYPO3_REQUEST']->getQueryParams() ?? [],
|
||||
'requestUri' => $GLOBALS['TYPO3_REQUEST']->getUri()->getPath() ?? ''
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// Query product
|
||||
$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 json_encode([
|
||||
'error' => 'Product not found',
|
||||
'debug' => $debugMode ? ['productUid' => $productUid] : null
|
||||
]);
|
||||
}
|
||||
|
||||
// Get product images
|
||||
$images = $this->getProductImages((int)$product['uid']);
|
||||
|
||||
// Get categories
|
||||
$categories = $this->getProductCategories((int)$product['uid']);
|
||||
|
||||
// Get downloads
|
||||
$downloads = $this->getProductDownloads((int)$product['uid']);
|
||||
|
||||
// Build response
|
||||
$response = [
|
||||
'product' => [
|
||||
'uid' => (int)$product['uid'],
|
||||
'title' => $product['title'],
|
||||
'subtitle' => $product['subtitle'],
|
||||
'slug' => $product['slug'],
|
||||
'teaser' => $product['teaser'],
|
||||
'description' => $product['description'],
|
||||
'seotitle' => $product['seotitle'],
|
||||
'categories' => $categories,
|
||||
'images' => $images,
|
||||
'downloads' => $downloads,
|
||||
],
|
||||
'layout' => $layout,
|
||||
'settings' => [
|
||||
'layout' => $layout,
|
||||
],
|
||||
];
|
||||
|
||||
if ($debugMode) {
|
||||
$response['debug'] = [
|
||||
'pageId' => $pageId,
|
||||
'productUid' => $productUid,
|
||||
'layout' => $layout,
|
||||
'settings' => $settings,
|
||||
];
|
||||
}
|
||||
|
||||
return json_encode($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all images for a product with FAL and ImageService processing
|
||||
*/
|
||||
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();
|
||||
|
||||
// Process main image
|
||||
$processedImage = $imageService->applyProcessingInstructions(
|
||||
$fileReference,
|
||||
['width' => '1874c', 'height' => '625c']
|
||||
);
|
||||
|
||||
// Generate srcset
|
||||
$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) {
|
||||
// Skip invalid file references
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories for a product
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all downloads for a product
|
||||
*/
|
||||
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;
|
||||
|
||||
// Get file information from FAL if file reference exists
|
||||
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;
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\View;
|
||||
|
||||
use TYPO3\CMS\Backend\View\BackendLayout\BackendLayout;
|
||||
use TYPO3\CMS\Backend\View\BackendLayout\BackendLayoutCollection;
|
||||
use TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext;
|
||||
use TYPO3\CMS\Backend\View\BackendLayout\DataProviderInterface;
|
||||
|
||||
/**
|
||||
* Provides one Backend Layout per VITEC frontend layout (pages.layout 0..20).
|
||||
*
|
||||
* Backend layout identifiers are the plain layout values: '0', '1', ... '20'.
|
||||
* Combined with the provider id 'vitec' (registered in ext_localconf.php) this
|
||||
* yields the standard combined identifier scheme vitec__<value>.
|
||||
*
|
||||
* The layout's title is shown above the page module and tells the editor
|
||||
* which frontend layout is currently active.
|
||||
*/
|
||||
final class BackendLayoutDataProvider implements DataProviderInterface
|
||||
{
|
||||
/** colPos values across all VITEC backend layouts.
|
||||
* Must NOT collide with container child colPos (>= 211). */
|
||||
private const COL_POS = [
|
||||
'main' => 0,
|
||||
'hero' => 1,
|
||||
'sidebar' => 2,
|
||||
'preFooter' => 3,
|
||||
];
|
||||
|
||||
/** Per-frontend-layout: title + ordered list of zones. */
|
||||
public const LAYOUT_MAP = [
|
||||
0 => ['title' => 'Default', 'zones' => ['main']],
|
||||
1 => ['title' => 'Index Page', 'zones' => ['hero', 'main', 'preFooter']],
|
||||
2 => ['title' => 'Markets Overview', 'zones' => ['hero', 'main']],
|
||||
3 => ['title' => 'Market Detail', 'zones' => ['hero', 'main', 'sidebar']],
|
||||
4 => ['title' => 'Solutions Overview', 'zones' => ['hero', 'main']],
|
||||
5 => ['title' => 'Solution Detail', 'zones' => ['hero', 'main', 'sidebar']],
|
||||
6 => ['title' => 'Use Cases Overview', 'zones' => ['hero', 'main']],
|
||||
7 => ['title' => 'Use Case Detail', 'zones' => ['hero', 'main', 'sidebar']],
|
||||
8 => ['title' => 'Products Overview', 'zones' => ['hero', 'main']],
|
||||
9 => ['title' => 'Product Main Category', 'zones' => ['hero', 'main', 'preFooter']],
|
||||
10 => ['title' => 'Product Detail', 'zones' => ['hero', 'main', 'sidebar']],
|
||||
11 => ['title' => 'Support Overview', 'zones' => ['hero', 'main']],
|
||||
12 => ['title' => 'News Overview', 'zones' => ['hero', 'main']],
|
||||
13 => ['title' => 'News Detail V1', 'zones' => ['hero', 'main', 'sidebar']],
|
||||
14 => ['title' => 'News Detail V2', 'zones' => ['hero', 'main']],
|
||||
15 => ['title' => 'News Detail V3', 'zones' => ['hero', 'main', 'sidebar', 'preFooter']],
|
||||
16 => ['title' => 'Content Page V1', 'zones' => ['hero', 'main']],
|
||||
17 => ['title' => 'Content Page V2', 'zones' => ['hero', 'main', 'sidebar']],
|
||||
18 => ['title' => 'Content Page V3', 'zones' => ['hero', 'main', 'sidebar', 'preFooter']],
|
||||
19 => ['title' => 'Events Overview', 'zones' => ['hero', 'main']],
|
||||
20 => ['title' => 'Contact Page', 'zones' => ['hero', 'main', 'sidebar']],
|
||||
];
|
||||
|
||||
/** Display name for each zone. Shown as column header in the BE page module. */
|
||||
private const ZONE_NAMES = [
|
||||
'main' => 'Main Content',
|
||||
'hero' => 'Hero',
|
||||
'sidebar' => 'Sidebar',
|
||||
'preFooter' => 'Pre-Footer',
|
||||
];
|
||||
|
||||
public function addBackendLayouts(DataProviderContext $dataProviderContext, BackendLayoutCollection $backendLayoutCollection)
|
||||
{
|
||||
foreach (self::LAYOUT_MAP as $value => $info) {
|
||||
$backendLayoutCollection->add($this->buildBackendLayout((int)$value, $info['title'], $info['zones']));
|
||||
}
|
||||
}
|
||||
|
||||
public function getBackendLayout($identifier, $pageId)
|
||||
{
|
||||
$value = (int)$identifier;
|
||||
if (!isset(self::LAYOUT_MAP[$value])) {
|
||||
return null;
|
||||
}
|
||||
$info = self::LAYOUT_MAP[$value];
|
||||
return $this->buildBackendLayout($value, $info['title'], $info['zones']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $zones
|
||||
*/
|
||||
private function buildBackendLayout(int $value, string $title, array $zones): BackendLayout
|
||||
{
|
||||
$rowsTs = '';
|
||||
$rowNum = 1;
|
||||
foreach ($zones as $zoneKey) {
|
||||
$name = self::ZONE_NAMES[$zoneKey];
|
||||
$colPos = self::COL_POS[$zoneKey];
|
||||
$rowsTs .= <<<TS
|
||||
{$rowNum} {
|
||||
columns {
|
||||
1 {
|
||||
name = {$name}
|
||||
colPos = {$colPos}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TS;
|
||||
$rowNum++;
|
||||
}
|
||||
$rowCount = count($zones);
|
||||
$configuration = <<<TS
|
||||
backend_layout {
|
||||
colCount = 1
|
||||
rowCount = {$rowCount}
|
||||
rows {
|
||||
{$rowsTs} }
|
||||
}
|
||||
TS;
|
||||
|
||||
// Identifier is plain numeric; combined form becomes "vitec__<value>"
|
||||
return new BackendLayout(
|
||||
(string)$value,
|
||||
'VITEC Layout: ' . $title,
|
||||
$configuration
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\View;
|
||||
|
||||
use TYPO3\CMS\Extbase\Mvc\View\AbstractView;
|
||||
|
||||
/**
|
||||
* JSON View for Headless API
|
||||
*/
|
||||
class ProductListJsonView extends AbstractView
|
||||
{
|
||||
public function render(): string
|
||||
{
|
||||
$products = $this->variables['products'] ?? [];
|
||||
$productsData = [];
|
||||
|
||||
foreach ($products as $product) {
|
||||
$categories = [];
|
||||
if ($product->getCategories()) {
|
||||
foreach ($product->getCategories() as $category) {
|
||||
$categories[] = [
|
||||
'uid' => $category->getUid(),
|
||||
'title' => $category->getTitle(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$image = null;
|
||||
if ($product->getProductimage() && $product->getProductimage()->count() > 0) {
|
||||
$imageObject = $product->getProductimage()->current();
|
||||
$image = [
|
||||
'uid' => $imageObject->getUid(),
|
||||
'url' => $imageObject->getOriginalResource()->getPublicUrl(),
|
||||
'title' => $imageObject->getTitle(),
|
||||
'alternative' => $imageObject->getAlternative(),
|
||||
'description' => $imageObject->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
$productsData[] = [
|
||||
'uid' => $product->getUid(),
|
||||
'title' => $product->getTitle(),
|
||||
'subtitle' => $product->getSubtitle(),
|
||||
'teaser' => $product->getTeaser(),
|
||||
'description' => $product->getDescription(),
|
||||
'slug' => $product->getSlug(),
|
||||
'categories' => $categories,
|
||||
'image' => $image,
|
||||
];
|
||||
}
|
||||
|
||||
return json_encode([
|
||||
'products' => $productsData,
|
||||
], JSON_THROW_ON_ERROR);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Evomedien\Vitec\View;
|
||||
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\BackendLayoutView;
|
||||
|
||||
/**
|
||||
* Overrides BackendLayoutView so the page module always reflects pages.layout
|
||||
* without the editor having to set backend_layout manually.
|
||||
*/
|
||||
final class VitecBackendLayoutView extends BackendLayoutView
|
||||
{
|
||||
public function getSelectedCombinedIdentifier(int $pageId): string|false
|
||||
{
|
||||
if ($pageId <= 0) {
|
||||
return parent::getSelectedCombinedIdentifier($pageId);
|
||||
}
|
||||
|
||||
$page = BackendUtility::getRecord('pages', $pageId, 'layout');
|
||||
if (!is_array($page) || !isset($page['layout'])) {
|
||||
return parent::getSelectedCombinedIdentifier($pageId);
|
||||
}
|
||||
|
||||
// Combined identifier format: "<dataProvider>__<layoutId>"
|
||||
return 'vitec__' . (int)$page['layout'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace Evomedien\Vitec\Widgets;
|
||||
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetInterface;
|
||||
use TYPO3\CMS\Dashboard\Widgets\WidgetConfiguration;
|
||||
|
||||
class VitecWidget implements WidgetInterface
|
||||
{
|
||||
private WidgetConfiguration $configuration;
|
||||
|
||||
public function __construct(WidgetConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
// Render the widget container
|
||||
return '<div class="vitec-widget">' . $this->renderWidgetContent() . '</div>';
|
||||
}
|
||||
|
||||
public function renderWidgetContent(): string
|
||||
{
|
||||
// Render the actual widget content
|
||||
return '<div>Welcome to the Vitec custom widget!</div>';
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
// Return widget options (if any)
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getConfiguration(): WidgetConfiguration
|
||||
{
|
||||
return $this->configuration;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user