[TASK] Make FluidTemplateContentObjectTest notice free
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / ContentObject / FluidTemplateContentObject.php
1 <?php
2 namespace TYPO3\CMS\Frontend\ContentObject;
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\TypoScript\TypoScriptService;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Core\Utility\StringUtility;
20 use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
21 use TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder;
22 use TYPO3\CMS\Extbase\Object\ObjectManager;
23 use TYPO3\CMS\Fluid\View\StandaloneView;
24 use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
25
26 /**
27 * Contains FLUIDTEMPLATE class object
28 */
29 class FluidTemplateContentObject extends AbstractContentObject
30 {
31 /**
32 * @var StandaloneView
33 */
34 protected $view;
35
36 /**
37 * @var ContentDataProcessor
38 */
39 protected $contentDataProcessor;
40
41 /**
42 * @param ContentObjectRenderer $cObj
43 */
44 public function __construct(ContentObjectRenderer $cObj)
45 {
46 parent::__construct($cObj);
47 $this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class);
48 }
49
50 /**
51 * @param ContentDataProcessor $contentDataProcessor
52 */
53 public function setContentDataProcessor($contentDataProcessor)
54 {
55 $this->contentDataProcessor = $contentDataProcessor;
56 }
57
58 /**
59 * Rendering the cObject, FLUIDTEMPLATE
60 *
61 * Configuration properties:
62 * - file string+stdWrap The FLUID template file
63 * - layoutRootPaths array of filepath+stdWrap Root paths to layouts (fallback)
64 * - partialRootPaths array of filepath+stdWrap Root paths to partials (fallback)
65 * - variable array of cObjects, the keys are the variable names in fluid
66 * - dataProcessing array of data processors which are classes to manipulate $data
67 * - extbase.pluginName
68 * - extbase.controllerExtensionName
69 * - extbase.controllerName
70 * - extbase.controllerActionName
71 *
72 * Example:
73 * 10 = FLUIDTEMPLATE
74 * 10.templateName = MyTemplate
75 * 10.templateRootPaths.10 = EXT:site_configuration/Resources/Private/Templates/
76 * 10.partialRootPaths.10 = EXT:site_configuration/Resources/Private/Patials/
77 * 10.layoutRootPaths.10 = EXT:site_configuration/Resources/Private/Layouts/
78 * 10.variables {
79 * mylabel = TEXT
80 * mylabel.value = Label from TypoScript coming
81 * }
82 *
83 * @param array $conf Array of TypoScript properties
84 * @return string The HTML output
85 */
86 public function render($conf = [])
87 {
88 $parentView = $this->view;
89 $this->initializeStandaloneViewInstance();
90
91 if (!is_array($conf)) {
92 $conf = [];
93 }
94
95 $this->setFormat($conf);
96 $this->setTemplate($conf);
97 $this->setLayoutRootPath($conf);
98 $this->setPartialRootPath($conf);
99 $this->setExtbaseVariables($conf);
100 $this->assignSettings($conf);
101 $variables = $this->getContentObjectVariables($conf);
102 $variables = $this->contentDataProcessor->process($this->cObj, $conf, $variables);
103
104 $this->view->assignMultiple($variables);
105
106 $this->renderFluidTemplateAssetsIntoPageRenderer();
107 $content = $this->renderFluidView();
108 $content = $this->applyStandardWrapToRenderedContent($content, $conf);
109
110 $this->view = $parentView;
111 return $content;
112 }
113
114 /**
115 * Attempts to render HeaderAssets and FooterAssets sections from the
116 * Fluid template, then adds each (if not empty) to either header or
117 * footer, as appropriate, using PageRenderer.
118 */
119 protected function renderFluidTemplateAssetsIntoPageRenderer()
120 {
121 $pageRenderer = $this->getPageRenderer();
122 $headerAssets = $this->view->renderSection('HeaderAssets', ['contentObject' => $this], true);
123 $footerAssets = $this->view->renderSection('FooterAssets', ['contentObject' => $this], true);
124 if (!empty(trim($headerAssets))) {
125 $pageRenderer->addHeaderData($headerAssets);
126 }
127 if (!empty(trim($footerAssets))) {
128 $pageRenderer->addFooterData($footerAssets);
129 }
130 }
131
132 /**
133 * Creating standalone view instance must not be done in construct() as
134 * it can lead to a nasty cache issue since content object instances
135 * are not always re-created by the content object rendered for every
136 * usage, but can be re-used. Thus, we need a fresh instance of
137 * StandaloneView every time render() is called.
138 */
139 protected function initializeStandaloneViewInstance()
140 {
141 $this->view = GeneralUtility::makeInstance(StandaloneView::class);
142 }
143
144 /**
145 * Set template
146 *
147 * @param array $conf With possibly set file resource
148 * @throws \InvalidArgumentException
149 */
150 protected function setTemplate(array $conf)
151 {
152 // Fetch the Fluid template by templateName
153 if (
154 (!empty($conf['templateName']) || !empty($conf['templateName.']))
155 && !empty($conf['templateRootPaths.']) && is_array($conf['templateRootPaths.'])
156 ) {
157 $templateRootPaths = $this->applyStandardWrapToFluidPaths($conf['templateRootPaths.']);
158 $this->view->setTemplateRootPaths($templateRootPaths);
159 $templateName = isset($conf['templateName.'])
160 ? $this->cObj->stdWrap($conf['templateName'] ?? '', $conf['templateName.'])
161 : $conf['templateName'];
162 $this->view->setTemplate($templateName);
163 } elseif (!empty($conf['template']) && !empty($conf['template.'])) {
164 // Fetch the Fluid template by template cObject
165 $templateSource = $this->cObj->cObjGetSingle($conf['template'], $conf['template.']);
166 if ($templateSource === '') {
167 throw new ContentRenderingException(
168 'Could not find template source for ' . $conf['template'],
169 1437420865
170 );
171 }
172 $this->view->setTemplateSource($templateSource);
173 } else {
174 // Fetch the Fluid template by file stdWrap
175 $file = isset($conf['file.'])
176 ? $this->cObj->stdWrap($conf['file'] ?? '', $conf['file.'])
177 : ($conf['file'] ?? '');
178 // Get the absolute file name
179 $templatePathAndFilename = GeneralUtility::getFileAbsFileName($file);
180 $this->view->setTemplatePathAndFilename($templatePathAndFilename);
181 }
182 }
183
184 /**
185 * Set layout root path if given in configuration
186 *
187 * @param array $conf Configuration array
188 */
189 protected function setLayoutRootPath(array $conf)
190 {
191 // Override the default layout path via typoscript
192 $layoutPaths = [];
193 if (isset($conf['layoutRootPath']) || isset($conf['layoutRootPath.'])) {
194 $layoutRootPath = isset($conf['layoutRootPath.'])
195 ? $this->cObj->stdWrap($conf['layoutRootPath'], $conf['layoutRootPath.'])
196 : $conf['layoutRootPath'];
197 $layoutPaths[] = GeneralUtility::getFileAbsFileName($layoutRootPath);
198 }
199 if (isset($conf['layoutRootPaths.'])) {
200 $layoutPaths = array_replace($layoutPaths, $this->applyStandardWrapToFluidPaths($conf['layoutRootPaths.']));
201 }
202 if (!empty($layoutPaths)) {
203 $this->view->setLayoutRootPaths($layoutPaths);
204 }
205 }
206
207 /**
208 * Set partial root path if given in configuration
209 *
210 * @param array $conf Configuration array
211 */
212 protected function setPartialRootPath(array $conf)
213 {
214 $partialPaths = [];
215 if (isset($conf['partialRootPath']) || isset($conf['partialRootPath.'])) {
216 $partialRootPath = isset($conf['partialRootPath.'])
217 ? $this->cObj->stdWrap($conf['partialRootPath'], $conf['partialRootPath.'])
218 : $conf['partialRootPath'];
219 $partialPaths[] = GeneralUtility::getFileAbsFileName($partialRootPath);
220 }
221 if (isset($conf['partialRootPaths.'])) {
222 $partialPaths = array_replace($partialPaths, $this->applyStandardWrapToFluidPaths($conf['partialRootPaths.']));
223 }
224 if (!empty($partialPaths)) {
225 $this->view->setPartialRootPaths($partialPaths);
226 }
227 }
228
229 /**
230 * Set different format if given in configuration
231 *
232 * @param array $conf Configuration array
233 */
234 protected function setFormat(array $conf)
235 {
236 $format = isset($conf['format.'])
237 ? $this->cObj->stdWrap($conf['format'] ?? '', $conf['format.'])
238 : ($conf['format'] ?? '');
239 if ($format) {
240 $this->view->setFormat($format);
241 }
242 }
243
244 /**
245 * Set some extbase variables if given
246 *
247 * @param array $conf Configuration array
248 */
249 protected function setExtbaseVariables(array $conf)
250 {
251 /** @var $request \TYPO3\CMS\Extbase\Mvc\Request */
252 $requestPluginName = isset($conf['extbase.']['pluginName.'])
253 ? $this->cObj->stdWrap($conf['extbase.']['pluginName'] ?? '', $conf['extbase.']['pluginName.'])
254 : ($conf['extbase.']['pluginName'] ?? '');
255 if ($requestPluginName) {
256 $this->view->getRequest()->setPluginName($requestPluginName);
257 }
258 $requestControllerExtensionName = isset($conf['extbase.']['controllerExtensionName.'])
259 ? $this->cObj->stdWrap($conf['extbase.']['controllerExtensionName'] ?? '', $conf['extbase.']['controllerExtensionName.'])
260 : ($conf['extbase.']['controllerExtensionName'] ?? '');
261 if ($requestControllerExtensionName) {
262 $this->view->getRequest()->setControllerExtensionName($requestControllerExtensionName);
263 }
264 $requestControllerName = isset($conf['extbase.']['controllerName.'])
265 ? $this->cObj->stdWrap($conf['extbase.']['controllerName'] ?? '', $conf['extbase.']['controllerName.'])
266 : ($conf['extbase.']['controllerName'] ?? '');
267 if ($requestControllerName) {
268 $this->view->getRequest()->setControllerName($requestControllerName);
269 }
270 $requestControllerActionName = isset($conf['extbase.']['controllerActionName.'])
271 ? $this->cObj->stdWrap($conf['extbase.']['controllerActionName'] ?? '', $conf['extbase.']['controllerActionName.'])
272 : ($conf['extbase.']['controllerActionName'] ?? '');
273 if ($requestControllerActionName) {
274 $this->view->getRequest()->setControllerActionName($requestControllerActionName);
275 }
276
277 if (
278 $requestPluginName
279 && $requestControllerExtensionName
280 && $requestControllerName
281 && $requestControllerActionName
282 ) {
283 $configurationManager = GeneralUtility::makeInstance(ObjectManager::class)->get(ConfigurationManager::class);
284 $configurationManager->setConfiguration([
285 'extensionName' => $requestControllerExtensionName,
286 'pluginName' => $requestPluginName,
287 ]);
288
289 if (!isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$requestControllerExtensionName]['plugins'][$requestPluginName]['controllers'])) {
290 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$requestControllerExtensionName]['plugins'][$requestPluginName]['controllers'] = [
291 $requestControllerName => [
292 'actions' => [
293 $requestControllerActionName,
294 ],
295 ],
296 ];
297 }
298
299 $requestBuilder = GeneralUtility::makeInstance(ObjectManager::class)->get(RequestBuilder::class);
300 $this->view->getRenderingContext()->getControllerContext()->setRequest($requestBuilder->build());
301 }
302 }
303
304 /**
305 * Compile rendered content objects in variables array ready to assign to the view
306 *
307 * @param array $conf Configuration array
308 * @return array the variables to be assigned
309 * @throws \InvalidArgumentException
310 */
311 protected function getContentObjectVariables(array $conf)
312 {
313 $variables = [];
314 $reservedVariables = ['data', 'current'];
315 // Accumulate the variables to be process and loop them through cObjGetSingle
316 $variablesToProcess = (array)($conf['variables.'] ?? []);
317 foreach ($variablesToProcess as $variableName => $cObjType) {
318 if (is_array($cObjType)) {
319 continue;
320 }
321 if (!in_array($variableName, $reservedVariables)) {
322 $variables[$variableName] = $this->cObj->cObjGetSingle($cObjType, $variablesToProcess[$variableName . '.']);
323 } else {
324 throw new \InvalidArgumentException(
325 'Cannot use reserved name "' . $variableName . '" as variable name in FLUIDTEMPLATE.',
326 1288095720
327 );
328 }
329 }
330 $variables['data'] = $this->cObj->data;
331 $variables['current'] = $this->cObj->data[$this->cObj->currentValKey ?? null] ?? null;
332 return $variables;
333 }
334
335 /**
336 * Set any TypoScript settings to the view. This is similar to a
337 * default MVC action controller in extbase.
338 *
339 * @param array $conf Configuration
340 */
341 protected function assignSettings(array $conf)
342 {
343 if (isset($conf['settings.'])) {
344 /** @var $typoScriptService TypoScriptService */
345 $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
346 $settings = $typoScriptService->convertTypoScriptArrayToPlainArray($conf['settings.']);
347 $this->view->assign('settings', $settings);
348 }
349 }
350
351 /**
352 * Render fluid standalone view
353 *
354 * @return string
355 */
356 protected function renderFluidView()
357 {
358 return $this->view->render();
359 }
360
361 /**
362 * Apply standard wrap to content
363 *
364 * @param string $content Rendered HTML content
365 * @param array $conf Configuration array
366 * @return string Standard wrapped content
367 */
368 protected function applyStandardWrapToRenderedContent($content, array $conf)
369 {
370 if (isset($conf['stdWrap.'])) {
371 $content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
372 }
373 return $content;
374 }
375
376 /**
377 * Applies stdWrap on Fluid path definitions
378 *
379 * @param array $paths
380 *
381 * @return array
382 */
383 protected function applyStandardWrapToFluidPaths(array $paths)
384 {
385 $finalPaths = [];
386 foreach ($paths as $key => $path) {
387 if (StringUtility::endsWith($key, '.')) {
388 if (isset($paths[substr($key, 0, -1)])) {
389 continue;
390 }
391 $path = $this->cObj->stdWrap('', $path);
392 } elseif (isset($paths[$key . '.'])) {
393 $path = $this->cObj->stdWrap($path, $paths[$key . '.']);
394 }
395 $finalPaths[$key] = GeneralUtility::getFileAbsFileName($path);
396 }
397 return $finalPaths;
398 }
399 }