[TASK] Use null coalescing operator where possible
[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 = null;
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 if (!is_array($conf)) {
89 $conf = [];
90 }
91 $variables = $this->getContentObjectVariables($conf);
92 $variables = $this->contentDataProcessor->process($this->cObj, $conf, $variables);
93
94 $parentView = $this->view;
95 $this->initializeStandaloneViewInstance();
96
97 $this->setFormat($conf);
98 $this->setTemplate($conf);
99 $this->setLayoutRootPath($conf);
100 $this->setPartialRootPath($conf);
101 $this->setExtbaseVariables($conf);
102 $this->assignSettings($conf);
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.']) ? $this->cObj->stdWrap($conf['file'], $conf['file.']) : $conf['file'];
176 // Get the absolute file name
177 $templatePathAndFilename = GeneralUtility::getFileAbsFileName($file);
178 $this->view->setTemplatePathAndFilename($templatePathAndFilename);
179 }
180 }
181
182 /**
183 * Set layout root path if given in configuration
184 *
185 * @param array $conf Configuration array
186 */
187 protected function setLayoutRootPath(array $conf)
188 {
189 // Override the default layout path via typoscript
190 $layoutPaths = [];
191 if (isset($conf['layoutRootPath']) || isset($conf['layoutRootPath.'])) {
192 $layoutRootPath = isset($conf['layoutRootPath.'])
193 ? $this->cObj->stdWrap($conf['layoutRootPath'], $conf['layoutRootPath.'])
194 : $conf['layoutRootPath'];
195 $layoutPaths[] = GeneralUtility::getFileAbsFileName($layoutRootPath);
196 }
197 if (isset($conf['layoutRootPaths.'])) {
198 $layoutPaths = array_replace($layoutPaths, $this->applyStandardWrapToFluidPaths($conf['layoutRootPaths.']));
199 }
200 if (!empty($layoutPaths)) {
201 $this->view->setLayoutRootPaths($layoutPaths);
202 }
203 }
204
205 /**
206 * Set partial root path if given in configuration
207 *
208 * @param array $conf Configuration array
209 */
210 protected function setPartialRootPath(array $conf)
211 {
212 $partialPaths = [];
213 if (isset($conf['partialRootPath']) || isset($conf['partialRootPath.'])) {
214 $partialRootPath = isset($conf['partialRootPath.'])
215 ? $this->cObj->stdWrap($conf['partialRootPath'], $conf['partialRootPath.'])
216 : $conf['partialRootPath'];
217 $partialPaths[] = GeneralUtility::getFileAbsFileName($partialRootPath);
218 }
219 if (isset($conf['partialRootPaths.'])) {
220 $partialPaths = array_replace($partialPaths, $this->applyStandardWrapToFluidPaths($conf['partialRootPaths.']));
221 }
222 if (!empty($partialPaths)) {
223 $this->view->setPartialRootPaths($partialPaths);
224 }
225 }
226
227 /**
228 * Set different format if given in configuration
229 *
230 * @param array $conf Configuration array
231 */
232 protected function setFormat(array $conf)
233 {
234 $format = isset($conf['format.']) ? $this->cObj->stdWrap($conf['format'], $conf['format.']) : $conf['format'];
235 if ($format) {
236 $this->view->setFormat($format);
237 }
238 }
239
240 /**
241 * Set some extbase variables if given
242 *
243 * @param array $conf Configuration array
244 */
245 protected function setExtbaseVariables(array $conf)
246 {
247 /** @var $request \TYPO3\CMS\Extbase\Mvc\Request */
248 $requestPluginName = isset($conf['extbase.']['pluginName.']) ? $this->cObj->stdWrap($conf['extbase.']['pluginName'], $conf['extbase.']['pluginName.']) : $conf['extbase.']['pluginName'];
249 if ($requestPluginName) {
250 $this->view->getRequest()->setPluginName($requestPluginName);
251 }
252 $requestControllerExtensionName = isset($conf['extbase.']['controllerExtensionName.']) ? $this->cObj->stdWrap($conf['extbase.']['controllerExtensionName'], $conf['extbase.']['controllerExtensionName.']) : $conf['extbase.']['controllerExtensionName'];
253 if ($requestControllerExtensionName) {
254 $this->view->getRequest()->setControllerExtensionName($requestControllerExtensionName);
255 }
256 $requestControllerName = isset($conf['extbase.']['controllerName.']) ? $this->cObj->stdWrap($conf['extbase.']['controllerName'], $conf['extbase.']['controllerName.']) : $conf['extbase.']['controllerName'];
257 if ($requestControllerName) {
258 $this->view->getRequest()->setControllerName($requestControllerName);
259 }
260 $requestControllerActionName = isset($conf['extbase.']['controllerActionName.']) ? $this->cObj->stdWrap($conf['extbase.']['controllerActionName'], $conf['extbase.']['controllerActionName.']) : $conf['extbase.']['controllerActionName'];
261 if ($requestControllerActionName) {
262 $this->view->getRequest()->setControllerActionName($requestControllerActionName);
263 }
264
265 if (
266 $requestPluginName
267 && $requestControllerExtensionName
268 && $requestControllerName
269 && $requestControllerActionName
270 ) {
271 $configurationManager = GeneralUtility::makeInstance(ObjectManager::class)->get(ConfigurationManager::class);
272 $configurationManager->setConfiguration([
273 'extensionName' => $requestControllerExtensionName,
274 'pluginName' => $requestPluginName,
275 ]);
276
277 if (!isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$requestControllerExtensionName]['plugins'][$requestPluginName]['controllers'])) {
278 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['extensions'][$requestControllerExtensionName]['plugins'][$requestPluginName]['controllers'] = [
279 $requestControllerName => [
280 'actions' => [
281 $requestControllerActionName,
282 ],
283 ],
284 ];
285 }
286
287 $requestBuilder = GeneralUtility::makeInstance(ObjectManager::class)->get(RequestBuilder::class);
288 $this->view->getRenderingContext()->getControllerContext()->setRequest($requestBuilder->build());
289 }
290 }
291
292 /**
293 * Compile rendered content objects in variables array ready to assign to the view
294 *
295 * @param array $conf Configuration array
296 * @return array the variables to be assigned
297 * @throws \InvalidArgumentException
298 */
299 protected function getContentObjectVariables(array $conf)
300 {
301 $variables = [];
302 $reservedVariables = ['data', 'current'];
303 // Accumulate the variables to be process and loop them through cObjGetSingle
304 $variablesToProcess = (array)$conf['variables.'];
305 foreach ($variablesToProcess as $variableName => $cObjType) {
306 if (is_array($cObjType)) {
307 continue;
308 }
309 if (!in_array($variableName, $reservedVariables)) {
310 $variables[$variableName] = $this->cObj->cObjGetSingle($cObjType, $variablesToProcess[$variableName . '.']);
311 } else {
312 throw new \InvalidArgumentException(
313 'Cannot use reserved name "' . $variableName . '" as variable name in FLUIDTEMPLATE.',
314 1288095720
315 );
316 }
317 }
318 $variables['data'] = $this->cObj->data;
319 $variables['current'] = $this->cObj->data[$this->cObj->currentValKey];
320 return $variables;
321 }
322
323 /**
324 * Set any TypoScript settings to the view. This is similar to a
325 * default MVC action controller in extbase.
326 *
327 * @param array $conf Configuration
328 */
329 protected function assignSettings(array $conf)
330 {
331 if (isset($conf['settings.'])) {
332 /** @var $typoScriptService TypoScriptService */
333 $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
334 $settings = $typoScriptService->convertTypoScriptArrayToPlainArray($conf['settings.']);
335 $this->view->assign('settings', $settings);
336 }
337 }
338
339 /**
340 * Render fluid standalone view
341 *
342 * @return string
343 */
344 protected function renderFluidView()
345 {
346 return $this->view->render();
347 }
348
349 /**
350 * Apply standard wrap to content
351 *
352 * @param string $content Rendered HTML content
353 * @param array $conf Configuration array
354 * @return string Standard wrapped content
355 */
356 protected function applyStandardWrapToRenderedContent($content, array $conf)
357 {
358 if (isset($conf['stdWrap.'])) {
359 $content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
360 }
361 return $content;
362 }
363
364 /**
365 * Applies stdWrap on Fluid path definitions
366 *
367 * @param array $paths
368 *
369 * @return array
370 */
371 protected function applyStandardWrapToFluidPaths(array $paths)
372 {
373 $finalPaths = [];
374 foreach ($paths as $key => $path) {
375 if (StringUtility::endsWith($key, '.')) {
376 if (isset($paths[substr($key, 0, -1)])) {
377 continue;
378 }
379 $path = $this->cObj->stdWrap('', $path);
380 } elseif (isset($paths[$key . '.'])) {
381 $path = $this->cObj->stdWrap($path, $paths[$key . '.']);
382 }
383 $finalPaths[$key] = GeneralUtility::getFileAbsFileName($path);
384 }
385 return $finalPaths;
386 }
387 }