| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- <?php
- declare(strict_types=1);
- namespace Opentalent\OtTemplating\ViewHelpers;
- use Psr\Http\Message\ServerRequestInterface;
- use TYPO3\CMS\Core\Context\Context;
- use TYPO3\CMS\Core\Routing\PageArguments;
- use TYPO3\CMS\Core\Site\Entity\SiteInterface;
- use TYPO3\CMS\Core\Site\SiteFinder;
- use TYPO3\CMS\Core\TimeTracker\TimeTracker;
- use TYPO3\CMS\Core\Utility\GeneralUtility;
- use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
- use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
- use TYPO3\CMS\Fluid\Core\Rendering\RenderingContext;
- use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
- use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
- use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
- use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
- use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
- use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
- use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic;
- /**
- * This view helper is a modified version of the TYPO3\CMS\Fluid\ViewHelpers\CObjectViewHelper class
- * that allow to override the TS setup of the object by given variables
- *
- * It accepts the same arguments as \TYPO3\CMS\Fluid\ViewHelpers\CObjectViewHelper,
- * and an additional argument 'settings'
- *
- * @see https://docs.typo3.org/p/georgringer/news/master/en-us/Introduction/Index.html
- *
- * example:
- *
- * {namespace ot=Opentalent\OtTemplating\ViewHelpers}
- *
- * <ot:cObject typoscriptObjectPath="lib.tx_ottemplating.widgets.news_list"
- * settings="{'settings.defaultPageUid': 1}" />
- *
- * @package Opentalent\OtTemplating\ViewHelpers
- */
- final class CObjectViewHelper extends AbstractViewHelper
- {
- use CompileWithContentArgumentAndRenderStatic;
- /**
- * 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 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, '');
- // <-- Additional paramter
- $this->registerArgument(
- 'settings',
- 'array',
- 'For each key in this array, the setup entry will be replaced by the corresponding value',
- false,
- []
- );
- }
- /**
- * Renders the TypoScript object in the given TypoScript setup path.
- *
- * @throws Exception
- */
- public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string
- {
- $data = $renderChildrenClosure();
- $typoscriptObjectPath = (string)$arguments['typoscriptObjectPath'];
- $currentValueKey = $arguments['currentValueKey'];
- $table = $arguments['table'];
- /** @var RenderingContext $renderingContext */
- $request = $renderingContext->getRequest();
- $contentObjectRenderer = self::getContentObjectRenderer($request);
- $contentObjectRenderer->setRequest($request);
- $tsfeBackup = null;
- if (!isset($GLOBALS['TSFE']) || !($GLOBALS['TSFE'] instanceof TypoScriptFrontendController)) {
- $tsfeBackup = self::simulateFrontendEnvironment();
- }
- $currentValue = null;
- if (is_object($data)) {
- $data = 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 = self::getConfigurationManager()->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
- foreach ($pathSegments as $segment) {
- if (!array_key_exists($segment . '.', $setup)) {
- throw new Exception(
- 'TypoScript object path "' . $typoscriptObjectPath . '" does not exist',
- 1253191023
- );
- }
- $setup = $setup[$segment . '.'];
- }
- if (!isset($setup[$lastSegment])) {
- throw new Exception(
- 'No Content Object definition found at TypoScript object path "' . $typoscriptObjectPath . '"',
- 1540246570
- );
- }
- // <---- Added by Opentalent to override the TS setup
- $setup = self::evalConfiguration($setup, $lastSegment, $arguments['settings']);
- // ----->
- $content = self::renderContentObject($contentObjectRenderer, $setup, $typoscriptObjectPath, $lastSegment);
- if (!isset($GLOBALS['TSFE']) || !($GLOBALS['TSFE'] instanceof TypoScriptFrontendController)) {
- self::resetFrontendEnvironment($tsfeBackup);
- }
- return $content;
- }
- /**
- * Renders single content object and increases time tracker stack pointer
- */
- protected static function renderContentObject(ContentObjectRenderer $contentObjectRenderer, array $setup, string $typoscriptObjectPath, string $lastSegment): string
- {
- $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
- if ($timeTracker->LR) {
- $timeTracker->push('/f:cObject/', '<' . $typoscriptObjectPath);
- }
- $timeTracker->incStackPointer();
- $content = $contentObjectRenderer->cObjGetSingle($setup[$lastSegment], $setup[$lastSegment . '.'] ?? [], $typoscriptObjectPath);
- $timeTracker->decStackPointer();
- if ($timeTracker->LR) {
- $timeTracker->pull($content);
- }
- return $content;
- }
- protected static function getConfigurationManager(): ConfigurationManagerInterface
- {
- // @todo: this should be replaced by DI once Fluid can handle DI properly
- return GeneralUtility::getContainer()->get(ConfigurationManagerInterface::class);
- }
- protected static function getContentObjectRenderer(ServerRequestInterface $request): ContentObjectRenderer
- {
- if (($GLOBALS['TSFE'] ?? null) instanceof TypoScriptFrontendController) {
- $tsfe = $GLOBALS['TSFE'];
- } else {
- $site = $request->getAttribute('site');
- if (!($site instanceof SiteInterface)) {
- $sites = GeneralUtility::makeInstance(SiteFinder::class)->getAllSites();
- $site = reset($sites);
- }
- $language = $request->getAttribute('language') ?? $site->getDefaultLanguage();
- $pageArguments = $request->getAttribute('routing') ?? new PageArguments(0, '0', []);
- $tsfe = GeneralUtility::makeInstance(
- TypoScriptFrontendController::class,
- GeneralUtility::makeInstance(Context::class),
- $site,
- $language,
- $pageArguments,
- GeneralUtility::makeInstance(FrontendUserAuthentication::class)
- );
- }
- $contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class, $tsfe);
- $parent = $request->getAttribute('currentContentObject');
- if ($parent instanceof ContentObjectRenderer) {
- $contentObjectRenderer->setParent($parent->data, $parent->currentRecord);
- }
- return $contentObjectRenderer;
- }
- /**
- * \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer->cObjGetSingle() relies on $GLOBALS['TSFE']
- */
- protected static function simulateFrontendEnvironment(): ?TypoScriptFrontendController
- {
- $tsfeBackup = $GLOBALS['TSFE'] ?? null;
- $GLOBALS['TSFE'] = new \stdClass();
- $GLOBALS['TSFE']->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
- return $tsfeBackup;
- }
- /**
- * Resets $GLOBALS['TSFE'] if it was previously changed by simulateFrontendEnvironment()
- */
- protected static function resetFrontendEnvironment(?TypoScriptFrontendController $tsfeBackup): void
- {
- $GLOBALS['TSFE'] = $tsfeBackup;
- }
- /**
- * Explicitly set argument name to be used as content.
- */
- public function resolveContentArgumentName(): string
- {
- return 'data';
- }
- /**
- * -- Additional method --
- * Recursively replace any {$var} value in the given configuration
- * if 'var' is a key in the 'overrideSetup' array
- *
- * @param array $setup
- * @param string $entry_name
- * @param array $override_settings
- * @return array
- */
- private static function evalConfiguration(array $setup, string $entry_name, array $override_settings) {
- $entry_setup = $setup[$entry_name . '.'];
- foreach ($override_settings as $key => $val) {
- $override = [];
- $path = explode('.', $key);
- foreach (array_reverse($path) as $i => $segment) {
- if ($i == 0) {
- $override[$segment] = $val;
- } else {
- $override = [$segment . '.' => $override];
- }
- }
- $entry_setup = self::merge($entry_setup, $override);
- }
- $setup[$entry_name . '.'] = $entry_setup;
- return $setup;
- }
- /**
- * -- Additional method --
- * Similar to array_merge_recursive, except that the end nodes of the
- * base array is replaced and not merged.
- *
- * @param $base_array
- * @param $override
- * @return mixed
- */
- private static function merge($base_array, $override) {
- foreach ($base_array as $key => $val) {
- if (!isset($override[$key])) {
- continue;
- }
- if (is_array($val)) {
- $base_array[$key] = self::merge($val, $override[$key]);
- } else {
- $base_array[$key] = $override[$key];
- }
- }
- return $base_array;
- }
- }
|