eb1409cf7f1bb73863733d678a9407a05370d8c4
[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\Extbase\Utility\ArrayUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Extbase\Service\TypoScriptService;
20 use TYPO3\CMS\Fluid\View\StandaloneView;
21
22 /**
23 * Contains FLUIDTEMPLATE class object
24 */
25 class FluidTemplateContentObject extends AbstractContentObject {
26
27 /**
28 * @var StandaloneView
29 */
30 protected $view = NULL;
31
32 /**
33 * Rendering the cObject, FLUIDTEMPLATE
34 *
35 * Configuration properties:
36 * - file string+stdWrap The FLUID template file
37 * - layoutRootPaths array of filepath+stdWrap Root paths to layouts (fallback)
38 * - partialRootPaths array of filepath+stdWrap Root paths to partials (fallback)
39 * - variable array of cObjects, the keys are the variable names in fluid
40 * - dataProcessing array of data processors which are classes to manipulate $data
41 * - extbase.pluginName
42 * - extbase.controllerExtensionName
43 * - extbase.controllerName
44 * - extbase.controllerActionName
45 *
46 * Example:
47 * 10 = FLUIDTEMPLATE
48 * 10.templateName = MyTemplate
49 * 10.templateRootPaths.10 = EXT:site_configuration/Resources/Private/Templates/
50 * 10.partialRootPaths.10 = EXT:site_configuration/Resources/Private/Patials/
51 * 10.layoutRootPaths.10 = EXT:site_configuration/Resources/Private/Layouts/
52 * 10.variables {
53 * mylabel = TEXT
54 * mylabel.value = Label from TypoScript coming
55 * }
56 *
57 * @param array $conf Array of TypoScript properties
58 * @return string The HTML output
59 */
60 public function render($conf = array()) {
61 $parentView = $this->view;
62 $this->initializeStandaloneViewInstance();
63
64 if (!is_array($conf)) {
65 $conf = array();
66 }
67
68 $this->setFormat($conf);
69 $this->setTemplate($conf);
70 $this->setLayoutRootPath($conf);
71 $this->setPartialRootPath($conf);
72 $this->setExtbaseVariables($conf);
73 $this->assignSettings($conf);
74 $this->assignContentObjectVariables($conf);
75 $this->processData($conf);
76 $this->assignContentObjectDataAndCurrent();
77
78 $content = $this->renderFluidView();
79 $content = $this->applyStandardWrapToRenderedContent($content, $conf);
80
81 $this->view = $parentView;
82 return $content;
83 }
84
85 /**
86 * Creating standalone view instance must not be done in construct() as
87 * it can lead to a nasty cache issue since content object instances
88 * are not always re-created by the content object rendered for every
89 * usage, but can be re-used. Thus, we need a fresh instance of
90 * StandaloneView every time render() is called.
91 *
92 * @return void
93 */
94 protected function initializeStandaloneViewInstance() {
95 $this->view = GeneralUtility::makeInstance(StandaloneView::class);
96 }
97
98 /**
99 * Set template
100 *
101 * @param array $conf With possibly set file resource
102 * @return void
103 * @throws \InvalidArgumentException
104 */
105 protected function setTemplate(array $conf) {
106 // Fetch the Fluid template by templateName
107 if (!empty($conf['templateName']) && !empty($conf['templateRootPaths.']) && is_array($conf['templateRootPaths.'])) {
108 $templateRootPaths = array();
109 foreach ($conf['templateRootPaths.'] as $key => $path) {
110 $templateRootPaths[$key] = GeneralUtility::getFileAbsFileName($path);
111 }
112 $this->view->setTemplateRootPaths($templateRootPaths);
113 $templateName = isset($conf['templateName.']) ? $this->cObj->stdWrap($conf['templateName'], $conf['templateName.']) : $conf['templateName'];
114 $this->view->setTemplate($templateName);
115 // Fetch the Fluid template by template cObject
116 } elseif (!empty($conf['template']) && !empty($conf['template.'])) {
117 $templateSource = $this->cObj->cObjGetSingle($conf['template'], $conf['template.']);
118 $this->view->setTemplateSource($templateSource);
119 // Fetch the Fluid template by file stdWrap
120 } else {
121 $file = isset($conf['file.']) ? $this->cObj->stdWrap($conf['file'], $conf['file.']) : $conf['file'];
122 /** @var $templateService \TYPO3\CMS\Core\TypoScript\TemplateService */
123 $templateService = $GLOBALS['TSFE']->tmpl;
124 $templatePathAndFilename = $templateService->getFileName($file);
125 $this->view->setTemplatePathAndFilename(PATH_site . $templatePathAndFilename);
126 }
127 }
128
129 /**
130 * Set layout root path if given in configuration
131 *
132 * @param array $conf Configuration array
133 * @return void
134 */
135 protected function setLayoutRootPath(array $conf) {
136 // Override the default layout path via typoscript
137 $layoutPaths = array();
138 if (isset($conf['layoutRootPath']) || isset($conf['layoutRootPath.'])) {
139 $layoutRootPath = isset($conf['layoutRootPath.'])
140 ? $this->cObj->stdWrap($conf['layoutRootPath'], $conf['layoutRootPath.'])
141 : $conf['layoutRootPath'];
142 $layoutPaths[] = GeneralUtility::getFileAbsFileName($layoutRootPath);
143 }
144 if (isset($conf['layoutRootPaths.'])) {
145 foreach ($conf['layoutRootPaths.'] as $key => $path) {
146 $layoutPaths[$key] = GeneralUtility::getFileAbsFileName($path);
147 }
148 }
149 if (!empty($layoutPaths)) {
150 $this->view->setLayoutRootPaths($layoutPaths);
151 }
152 }
153
154 /**
155 * Set partial root path if given in configuration
156 *
157 * @param array $conf Configuration array
158 * @return void
159 */
160 protected function setPartialRootPath(array $conf) {
161 $partialPaths = array();
162 if (isset($conf['partialRootPath']) || isset($conf['partialRootPath.'])) {
163 $partialRootPath = isset($conf['partialRootPath.'])
164 ? $this->cObj->stdWrap($conf['partialRootPath'], $conf['partialRootPath.'])
165 : $conf['partialRootPath'];
166 $partialPaths[] = GeneralUtility::getFileAbsFileName($partialRootPath);
167 }
168 if (isset($conf['partialRootPaths.'])) {
169 foreach ($conf['partialRootPaths.'] as $key => $path) {
170 $partialPaths[$key] = GeneralUtility::getFileAbsFileName($path);
171 }
172 }
173 if (!empty($partialPaths)) {
174 $this->view->setPartialRootPaths($partialPaths);
175 }
176 }
177
178 /**
179 * Set different format if given in configuration
180 *
181 * @param array $conf Configuration array
182 * @return void
183 */
184 protected function setFormat(array $conf) {
185 $format = isset($conf['format.']) ? $this->cObj->stdWrap($conf['format'], $conf['format.']) : $conf['format'];
186 if ($format) {
187 $this->view->setFormat($format);
188 }
189 }
190
191 /**
192 * Set some extbase variables if given
193 *
194 * @param array $conf Configuration array
195 * @return void
196 */
197 protected function setExtbaseVariables(array $conf) {
198 /** @var $request \TYPO3\CMS\Extbase\Mvc\Request */
199 $requestPluginName = isset($conf['extbase.']['pluginName.']) ? $this->cObj->stdWrap($conf['extbase.']['pluginName'], $conf['extbase.']['pluginName.']) : $conf['extbase.']['pluginName'];
200 if ($requestPluginName) {
201 $this->view->getRequest()->setPluginName($requestPluginName);
202 }
203 $requestControllerExtensionName = isset($conf['extbase.']['controllerExtensionName.']) ? $this->cObj->stdWrap($conf['extbase.']['controllerExtensionName'], $conf['extbase.']['controllerExtensionName.']) : $conf['extbase.']['controllerExtensionName'];
204 if ($requestControllerExtensionName) {
205 $this->view->getRequest()->setControllerExtensionName($requestControllerExtensionName);
206 }
207 $requestControllerName = isset($conf['extbase.']['controllerName.']) ? $this->cObj->stdWrap($conf['extbase.']['controllerName'], $conf['extbase.']['controllerName.']) : $conf['extbase.']['controllerName'];
208 if ($requestControllerName) {
209 $this->view->getRequest()->setControllerName($requestControllerName);
210 }
211 $requestControllerActionName = isset($conf['extbase.']['controllerActionName.']) ? $this->cObj->stdWrap($conf['extbase.']['controllerActionName'], $conf['extbase.']['controllerActionName.']) : $conf['extbase.']['controllerActionName'];
212 if ($requestControllerActionName) {
213 $this->view->getRequest()->setControllerActionName($requestControllerActionName);
214 }
215 }
216
217 /**
218 * Check for the availability of processors, defined in TypoScript, and use them for data processing
219 *
220 * @param array $configuration Configuration array
221 * @return void
222 * @throws \UnexpectedValueException
223 */
224 protected function processData(array $configuration) {
225 if (
226 !empty($configuration['dataProcessing.'])
227 && is_array($configuration['dataProcessing.'])
228 ) {
229 $processors = $configuration['dataProcessing.'];
230
231 foreach ($processors as $key => $className) {
232 if (strpos($key, '.') === FALSE && !empty($className)) {
233 $processor = GeneralUtility::makeInstance($className);
234
235 if (!$processor instanceof FluidTemplateDataProcessorInterface) {
236 throw new \UnexpectedValueException(
237 '$processor with class name "' . $className . '" ' .
238 'must implement interface "' . FluidTemplateDataProcessorInterface::class . '"',
239 1427455377
240 );
241 }
242
243 $processorConfiguration = isset($processors[$key . '.']) ? $processors[$key . '.'] : array();
244
245 $processor->process(
246 $this->cObj->data,
247 $processorConfiguration,
248 $configuration,
249 $this->view
250 );
251 }
252 }
253 }
254 }
255
256 /**
257 * Assign rendered content objects in variables array to view
258 *
259 * @param array $conf Configuration array
260 * @return void
261 * @throws \InvalidArgumentException
262 */
263 protected function assignContentObjectVariables(array $conf) {
264 $reservedVariables = array('data', 'current');
265 // Accumulate the variables to be replaced and loop them through cObjGetSingle
266 $variables = (array)$conf['variables.'];
267 foreach ($variables as $variableName => $cObjType) {
268 if (is_array($cObjType)) {
269 continue;
270 }
271 if (!in_array($variableName, $reservedVariables)) {
272 $this->view->assign(
273 $variableName,
274 $this->cObj->cObjGetSingle($cObjType, $variables[$variableName . '.'])
275 );
276 } else {
277 throw new \InvalidArgumentException(
278 'Cannot use reserved name "' . $variableName . '" as variable name in FLUIDTEMPLATE.',
279 1288095720
280 );
281 }
282 }
283 }
284
285 /**
286 * Set any TypoScript settings to the view. This is similar to a
287 * default MVC action controller in extbase.
288 *
289 * @param array $conf Configuration
290 * @return void
291 */
292 protected function assignSettings(array $conf) {
293 if (isset($conf['settings.'])) {
294 /** @var $typoScriptService TypoScriptService */
295 $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
296 $settings = $typoScriptService->convertTypoScriptArrayToPlainArray($conf['settings.']);
297 $this->view->assign('settings', $settings);
298 }
299 }
300
301 /**
302 * Assign content object renderer data and current to view
303 *
304 * @return void
305 */
306 protected function assignContentObjectDataAndCurrent() {
307 $this->view->assign('data', $this->cObj->data);
308 $this->view->assign('current', $this->cObj->data[$this->cObj->currentValKey]);
309 }
310
311 /**
312 * Render fluid standalone view
313 *
314 * @return string
315 */
316 protected function renderFluidView() {
317 return $this->view->render();
318 }
319
320 /**
321 * Apply standard wrap to content
322 *
323 * @param string $content Rendered HTML content
324 * @param array $conf Configuration array
325 * @return string Standard wrapped content
326 */
327 protected function applyStandardWrapToRenderedContent($content, array $conf) {
328 if (isset($conf['stdWrap.'])) {
329 $content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
330 }
331 return $content;
332 }
333 }