* ``` * * **Note:** You have to ensure proper escaping (`htmlspecialchars`/`intval`/etc.) on your own! * * @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-cobject */ final class CObjectViewHelper extends AbstractViewHelper { /** * Disable escaping of child nodes' output * * @var bool */ protected $escapeChildren = false; /** * Disable escaping of this node's output * * @var bool */ protected $escapeOutput = false; public function __construct( private readonly TimeTracker $timeTracker, private readonly ConfigurationManagerInterface $configurationManager, ) {} public function initializeArguments(): void { $this->registerArgument('data', 'mixed', 'the data to be used for rendering the cObject. Can be an object, array or string. If this argument is not set, child nodes will be used'); $this->registerArgument('typoscriptObjectPath', 'string', 'the TypoScript setup path of the TypoScript object to render', true); $this->registerArgument('currentValueKey', 'string', 'currentValueKey'); $this->registerArgument('table', 'string', 'the table name associated with "data" argument. Typically tt_content or one of your custom tables. This argument should be set if rendering a FILES cObject where file references are used, or if the data argument is a database record.', false, ''); } /** * Renders the TypoScript object in the given TypoScript setup path. */ public function render(): string { $data = $this->renderChildren() ?? []; $typoscriptObjectPath = (string)$this->arguments['typoscriptObjectPath']; $currentValueKey = $this->arguments['currentValueKey']; $table = $this->arguments['table']; if (!$this->renderingContext->hasAttribute(ServerRequestInterface::class)) { throw new \RuntimeException('Required request not found in RenderingContext', 1724243608); } $request = $this->renderingContext->getAttribute(ServerRequestInterface::class); $contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class); $contentObjectRenderer->setRequest($request); $parent = $request->getAttribute('currentContentObject'); if ($parent instanceof ContentObjectRenderer) { $contentObjectRenderer->setParent($parent->data, $parent->currentRecord); } $currentValue = null; if (is_object($data)) { $data = $data instanceof RecordInterface ? ($data->getRawRecord()?->toArray(true) ?? $data->toArray()) : ObjectAccess::getGettableProperties($data); } elseif (is_string($data) || is_numeric($data)) { $currentValue = (string)$data; $data = [$data]; } $contentObjectRenderer->start($data, $table); if ($currentValue !== null) { $contentObjectRenderer->setCurrentVal($currentValue); } elseif ($currentValueKey !== null && isset($data[$currentValueKey])) { $contentObjectRenderer->setCurrentVal($data[$currentValueKey]); } $pathSegments = GeneralUtility::trimExplode('.', $typoscriptObjectPath); $lastSegment = (string)array_pop($pathSegments); $setup = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); foreach ($pathSegments as $segment) { if (!array_key_exists($segment . '.', $setup)) { throw new InvalidArgumentValueException( 'TypoScript object path "' . $typoscriptObjectPath . '" does not exist', 1253191023 ); } $setup = $setup[$segment . '.']; } if (!isset($setup[$lastSegment])) { throw new InvalidArgumentValueException( 'No Content Object definition found at TypoScript object path "' . $typoscriptObjectPath . '"', 1540246570 ); } return $this->renderContentObject($contentObjectRenderer, $setup, $typoscriptObjectPath, $lastSegment); } /** * Renders single content object and increases time tracker stack pointer */ private function renderContentObject(ContentObjectRenderer $contentObjectRenderer, array $setup, string $typoscriptObjectPath, string $lastSegment): string { if ($this->timeTracker->LR) { $this->timeTracker->push('/f:cObject/', '<' . $typoscriptObjectPath); } $this->timeTracker->incStackPointer(); $content = $contentObjectRenderer->cObjGetSingle($setup[$lastSegment], $setup[$lastSegment . '.'] ?? [], $typoscriptObjectPath); $this->timeTracker->decStackPointer(); if ($this->timeTracker->LR) { $this->timeTracker->pull($content); } return $content; } /** * Explicitly set argument name to be used as content. */ public function getContentArgumentName(): string { return 'data'; } }