[BUGFIX] Avoid overwriting page context in TSFE->cObj by f:cObject
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / ViewHelpers / CObjectViewHelper.php
1 <?php
2 namespace TYPO3\CMS\Fluid\ViewHelpers;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
19 use TYPO3\CMS\Extbase\Object\ObjectManager;
20 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
21 use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
22 use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
23 use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic;
24
25 /**
26 * This ViewHelper renders CObjects from the global TypoScript configuration.
27 * NOTE: You have to ensure proper escaping (htmlspecialchars/intval/etc.) on your own!
28 *
29 * = Examples =
30 *
31 * <code title="Render lib object">
32 * <f:cObject typoscriptObjectPath="lib.someLibObject" />
33 * </code>
34 * <output>
35 * rendered lib.someLibObject
36 * </output>
37 *
38 * <code title="Specify cObject data & current value">
39 * <f:cObject typoscriptObjectPath="lib.customHeader" data="{article}" currentValueKey="title" />
40 * </code>
41 * <output>
42 * rendered lib.customHeader. data and current value will be available in TypoScript
43 * </output>
44 *
45 * <code title="inline notation">
46 * {article -> f:cObject(typoscriptObjectPath: 'lib.customHeader')}
47 * </code>
48 * <output>
49 * rendered lib.customHeader. data will be available in TypoScript
50 * </output>
51 */
52 class CObjectViewHelper extends AbstractViewHelper
53 {
54 use CompileWithContentArgumentAndRenderStatic;
55
56 /**
57 * Disable escaping of child nodes' output
58 *
59 * @var bool
60 */
61 protected $escapeChildren = false;
62
63 /**
64 * Disable escaping of this node's output
65 *
66 * @var bool
67 */
68 protected $escapeOutput = false;
69
70 /**
71 * @var \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController contains a backup of the current $GLOBALS['TSFE'] if used in BE mode
72 */
73 protected static $tsfeBackup;
74
75 /**
76 * Initialize arguments.
77 *
78 * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception
79 */
80 public function initializeArguments()
81 {
82 $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');
83 $this->registerArgument('typoscriptObjectPath', 'string', 'the TypoScript setup path of the TypoScript object to render', true);
84 $this->registerArgument('currentValueKey', 'string', 'currentValueKey');
85 $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, '');
86 }
87
88 /**
89 * Renders the TypoScript object in the given TypoScript setup path.
90 *
91 * @param array $arguments
92 * @param \Closure $renderChildrenClosure
93 * @param RenderingContextInterface $renderingContext
94 * @return mixed
95 * @throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
96 */
97 public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
98 {
99 $content = '';
100 $data = $renderChildrenClosure();
101 $typoscriptObjectPath = $arguments['typoscriptObjectPath'];
102 $currentValueKey = $arguments['currentValueKey'];
103 $table = $arguments['table'];
104 $contentObjectRenderer = static::getContentObjectRenderer();
105 if (TYPO3_MODE === 'BE') {
106 static::simulateFrontendEnvironment();
107 }
108 $currentValue = null;
109 if (is_object($data)) {
110 $data = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getGettableProperties($data);
111 } elseif (is_string($data) || is_numeric($data)) {
112 $currentValue = (string)$data;
113 $data = [$data];
114 }
115 $contentObjectRenderer->start($data, $table);
116 if ($currentValue !== null) {
117 $contentObjectRenderer->setCurrentVal($currentValue);
118 } elseif ($currentValueKey !== null && isset($data[$currentValueKey])) {
119 $contentObjectRenderer->setCurrentVal($data[$currentValueKey]);
120 }
121 $pathSegments = GeneralUtility::trimExplode('.', $typoscriptObjectPath);
122 $lastSegment = array_pop($pathSegments);
123 $setup = static::getConfigurationManager()->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
124 foreach ($pathSegments as $segment) {
125 if (!array_key_exists($segment . '.', $setup)) {
126 throw new \TYPO3\CMS\Fluid\Core\ViewHelper\Exception(
127 'TypoScript object path "' . $typoscriptObjectPath . '" does not exist',
128 1253191023
129 );
130 }
131 $setup = $setup[$segment . '.'];
132 }
133 if (!isset($setup[$lastSegment])) {
134 throw new \TYPO3\CMS\Fluid\Core\ViewHelper\Exception(
135 'No Content Object definition found at TypoScript object path "' . $typoscriptObjectPath . '"',
136 1540246570
137 );
138 }
139 $content = $contentObjectRenderer->cObjGetSingle($setup[$lastSegment], $setup[$lastSegment . '.'] ?? []);
140 if (TYPO3_MODE === 'BE') {
141 static::resetFrontendEnvironment();
142 }
143 return $content;
144 }
145
146 /**
147 * @return ConfigurationManagerInterface
148 */
149 protected static function getConfigurationManager()
150 {
151 return GeneralUtility::makeInstance(ObjectManager::class)->get(ConfigurationManagerInterface::class);
152 }
153
154 /**
155 * @return ContentObjectRenderer
156 */
157 protected static function getContentObjectRenderer()
158 {
159 return GeneralUtility::makeInstance(
160 ContentObjectRenderer::class,
161 $GLOBALS['TSFE'] ?? GeneralUtility::makeInstance(TypoScriptFrontendController::class, null, 0, 0)
162 );
163 }
164
165 /**
166 * Sets the $TSFE->cObjectDepthCounter in Backend mode
167 * This somewhat hacky work around is currently needed because the cObjGetSingle() function of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer relies on this setting
168 */
169 protected static function simulateFrontendEnvironment()
170 {
171 static::$tsfeBackup = $GLOBALS['TSFE'] ?? null;
172 $GLOBALS['TSFE'] = new \stdClass();
173 $GLOBALS['TSFE']->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
174 $GLOBALS['TSFE']->cObjectDepthCounter = 100;
175 }
176
177 /**
178 * Resets $GLOBALS['TSFE'] if it was previously changed by simulateFrontendEnvironment()
179 *
180 * @see simulateFrontendEnvironment()
181 */
182 protected static function resetFrontendEnvironment()
183 {
184 $GLOBALS['TSFE'] = static::$tsfeBackup;
185 }
186 }