* Raised Extbase and Fluid versions to 1.2.0RC1.
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / View / TemplateView.php
1 <?php
2
3 /* *
4 * This script belongs to the FLOW3 package "Fluid". *
5 * *
6 * It is free software; you can redistribute it and/or modify it under *
7 * the terms of the GNU Lesser General Public License as published by the *
8 * Free Software Foundation, either version 3 of the License, or (at your *
9 * option) any later version. *
10 * *
11 * This script is distributed in the hope that it will be useful, but *
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
13 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
14 * General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU Lesser General Public *
17 * License along with the script. *
18 * If not, see http://www.gnu.org/licenses/lgpl.html *
19 * *
20 * The TYPO3 project - inspiring people to share! *
21 * */
22
23 /**
24 * The main template view. Should be used as view if you want Fluid Templating
25 *
26 * @version $Id: TemplateView.php 2043 2010-03-16 08:49:45Z sebastian $
27 * @package Fluid
28 * @subpackage View
29 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
30 * @api
31 * @scope prototype
32 */
33 class Tx_Fluid_View_TemplateView extends Tx_Extbase_MVC_View_AbstractView implements Tx_Fluid_View_TemplateViewInterface {
34
35 /**
36 * @var Tx_Fluid_Core_Parser_TemplateParser
37 */
38 protected $templateParser;
39
40 /**
41 * Pattern to be resolved for @templateRoot in the other patterns.
42 * @var string
43 */
44 protected $templateRootPathPattern = '@packageResourcesPath/Private/Templates';
45
46 /**
47 * Pattern to be resolved for @partialRoot in the other patterns.
48 * @var string
49 */
50 protected $partialRootPathPattern = '@packageResourcesPath/Private/Partials';
51
52 /**
53 * Pattern to be resolved for @layoutRoot in the other patterns.
54 * @var string
55 */
56 protected $layoutRootPathPattern = '@packageResourcesPath/Private/Layouts';
57
58 /**
59 * Path to the template root. If NULL, then $this->templateRootPathPattern will be used.
60 * @var string
61 */
62 protected $templateRootPath = NULL;
63
64 /**
65 * Path to the partial root. If NULL, then $this->partialRootPathPattern will be used.
66 * @var string
67 */
68 protected $partialRootPath = NULL;
69
70 /**
71 * Path to the layout root. If NULL, then $this->layoutRootPathPattern will be used.
72 * @var string
73 */
74 protected $layoutRootPath = NULL;
75
76 /**
77 * File pattern for resolving the template file
78 * @var string
79 */
80 protected $templatePathAndFilenamePattern = '@templateRoot/@controller/@action.@format';
81
82 /**
83 * Directory pattern for global partials. Not part of the public API, should not be changed for now.
84 * @var string
85 */
86 private $partialPathAndFilenamePattern = '@partialRoot/@partial.@format';
87
88 /**
89 * File pattern for resolving the layout
90 * @var string
91 */
92 protected $layoutPathAndFilenamePattern = '@layoutRoot/@layout.@format';
93
94 /**
95 * Path and filename of the template file. If set, overrides the templatePathAndFilenamePattern
96 * @var string
97 */
98 protected $templatePathAndFilename = NULL;
99
100 /**
101 * Path and filename of the layout file. If set, overrides the layoutPathAndFilenamePattern
102 * @var string
103 */
104 protected $layoutPathAndFilename = NULL;
105
106 public function __construct() {
107 $this->templateParser = Tx_Fluid_Compatibility_TemplateParserBuilder::build();
108 $this->objectManager = t3lib_div::makeInstance('Tx_Fluid_Compatibility_ObjectManager');
109 }
110 // Here, the backporter can insert a constructor method, which is needed for Fluid v4.
111
112 /**
113 * Inject the template parser
114 *
115 * @param Tx_Fluid_Core_Parser_TemplateParser $templateParser The template parser
116 * @return void
117 * @author Sebastian Kurfürst <sebastian@typo3.org>
118 */
119 public function injectTemplateParser(Tx_Fluid_Core_Parser_TemplateParser $templateParser) {
120 $this->templateParser = $templateParser;
121 }
122
123 /**
124 * Initialize view
125 *
126 * @return void
127 * @author Sebastian Kurfürst <sebastian@typo3.org>
128 * @api
129 */
130 public function initializeView() {
131 }
132
133 /**
134 * Sets the path and name of of the template file. Effectively overrides the
135 * dynamic resolving of a template file.
136 *
137 * @param string $templatePathAndFilename Template file path
138 * @return void
139 * @author Sebastian Kurfürst <sebastian@typo3.org>
140 * @api
141 */
142 public function setTemplatePathAndFilename($templatePathAndFilename) {
143 $this->templatePathAndFilename = $templatePathAndFilename;
144 }
145
146 /**
147 * Sets the path and name of the layout file. Overrides the dynamic resolving of the layout file.
148 *
149 * @param string $layoutPathAndFilename Path and filename of the layout file
150 * @return void
151 * @author Sebastian Kurfürst <sebastian@typo3.org>
152 * @api
153 */
154 public function setLayoutPathAndFilename($layoutPathAndFilename) {
155 $this->layoutPathAndFilename = $layoutPathAndFilename;
156 }
157
158 /**
159 * Build the rendering context
160 *
161 * @param Tx_Fluid_Core_ViewHelper_TemplateVariableContainer $variableContainer
162 * @return Tx_Fluid_Core_Rendering_RenderingContext
163 * @author Sebastian Kurfürst <sebastian@typo3.org>
164 */
165 protected function buildRenderingContext(Tx_Fluid_Core_ViewHelper_TemplateVariableContainer $variableContainer = NULL) {
166 if ($variableContainer === NULL) {
167 $variableContainer = $this->objectManager->create('Tx_Fluid_Core_ViewHelper_TemplateVariableContainer', $this->variables);
168 }
169
170 $renderingContext = $this->objectManager->create('Tx_Fluid_Core_Rendering_RenderingContext');
171 $renderingContext->setTemplateVariableContainer($variableContainer);
172 if ($this->controllerContext !== NULL) {
173 $renderingContext->setControllerContext($this->controllerContext);
174 }
175
176 $viewHelperVariableContainer = $this->objectManager->create('Tx_Fluid_Core_ViewHelper_ViewHelperVariableContainer');
177 $viewHelperVariableContainer->setView($this);
178 $renderingContext->setViewHelperVariableContainer($viewHelperVariableContainer);
179
180 return $renderingContext;
181 }
182
183 /**
184 * Build parser configuration
185 *
186 * @return Tx_Fluid_Core_Parser_Configuration
187 * @author Karsten Dambekalns <karsten@typo3.org>
188 */
189 protected function buildParserConfiguration() {
190 $parserConfiguration = $this->objectManager->create('Tx_Fluid_Core_Parser_Configuration');
191 if ($this->controllerContext->getRequest()->getFormat() === 'html') {
192 $parserConfiguration->addInterceptor($this->objectManager->get('Tx_Fluid_Core_Parser_Interceptor_Escape'));
193
194 }
195 return $parserConfiguration;
196 }
197
198 /**
199 * Find the XHTML template according to $this->templatePathAndFilenamePattern and render the template.
200 * If "layoutName" is set in a PostParseFacet callback, it will render the file with the given layout.
201 *
202 * @param string $actionName If set, the view of the specified action will be rendered instead. Default is the action specified in the Request object
203 * @return string Rendered Template
204 * @author Sebastian Kurfürst <sebastian@typo3.org>
205 * @api
206 */
207 public function render($actionName = NULL) {
208 $templatePathAndFilename = $this->resolveTemplatePathAndFilename($actionName);
209
210 $this->templateParser->setConfiguration($this->buildParserConfiguration());
211 $parsedTemplate = $this->parseTemplate($templatePathAndFilename);
212
213 $variableContainer = $parsedTemplate->getVariableContainer();
214 if ($variableContainer !== NULL && $variableContainer->exists('layoutName')) {
215 return $this->renderWithLayout($variableContainer->get('layoutName'));
216 }
217
218 return $parsedTemplate->render($this->buildRenderingContext());
219 }
220
221 /**
222 * Resolve the template path and filename for the given action. If $actionName
223 * is NULL, looks into the current request.
224 *
225 * @param string $actionName Name of the action. If NULL, will be taken from request.
226 * @return string Full path to template
227 * @throws Tx_Fluid_View_Exception_InvalidTemplateResourceException
228 * @author Sebastian Kurfürst <sebastian@typo3.org>
229 */
230 protected function resolveTemplatePathAndFilename($actionName = NULL) {
231 if ($this->templatePathAndFilename !== NULL) {
232 return $this->templatePathAndFilename;
233 }
234
235 $actionName = ($actionName !== NULL ? $actionName : $this->controllerContext->getRequest()->getControllerActionName());
236 $actionName = ucfirst($actionName);
237
238 $paths = $this->expandGenericPathPattern($this->templatePathAndFilenamePattern, FALSE, FALSE);
239
240 foreach ($paths as &$path) {
241 // TODO remove fallback to lower case template files after grace period
242 $fallbackPath = str_replace('@action', strtolower($actionName), $path);
243 $path = str_replace('@action', $actionName, $path);
244 if (file_exists($path)) {
245 return $path;
246 } else {
247 if (file_exists($fallbackPath)) {
248 t3lib_div::deprecationLog('the template filename "' . $fallbackPath . '" is lowercase. This is deprecated since TYPO3 4.4. Please rename the template to "' . basename($path) . '"');
249 return $fallbackPath;
250 }
251 }
252 }
253 throw new Tx_Fluid_View_Exception_InvalidTemplateResourceException('Template could not be loaded. I tried "' . implode('", "', $paths) . '"', 1225709595);
254 }
255
256 /**
257 * Renders a given section.
258 *
259 * @param string $sectionName Name of section to render
260 * @return string rendered template for the section
261 * @throws Tx_Fluid_View_Exception_InvalidSectionException
262 * @author Sebastian Kurfürst <sebastian@typo3.org>
263 * @author Bastian Waidelich <bastian@typo3.org>
264 */
265 public function renderSection($sectionName) {
266 $parsedTemplate = $this->parseTemplate($this->resolveTemplatePathAndFilename());
267
268 $sections = $parsedTemplate->getVariableContainer()->get('sections');
269 if(!array_key_exists($sectionName, $sections)) {
270 throw new Tx_Fluid_View_Exception_InvalidSectionException('The given section does not exist!', 1227108982);
271 }
272 $section = $sections[$sectionName];
273
274 $renderingContext = $this->buildRenderingContext();
275 $section->setRenderingContext($renderingContext);
276 return $section->evaluate();
277 }
278
279 /**
280 * Render a template with a given layout.
281 *
282 * @param string $layoutName Name of layout
283 * @return string rendered HTML
284 * @author Sebastian Kurfürst <sebastian@typo3.org>
285 * @author Bastian Waidelich <bastian@typo3.org>
286 */
287 public function renderWithLayout($layoutName) {
288 $parsedTemplate = $this->parseTemplate($this->resolveLayoutPathAndFilename($layoutName));
289
290 $renderingContext = $this->buildRenderingContext();
291 return $parsedTemplate->render($renderingContext);
292 }
293
294 /**
295 * Resolve the path and file name of the layout file, based on
296 * $this->layoutPathAndFilename and $this->layoutPathAndFilenamePattern.
297 *
298 * In case a layout has already been set with setLayoutPathAndFilename(),
299 * this method returns that path, otherwise a path and filename will be
300 * resolved using the layoutPathAndFilenamePattern.
301 *
302 * @param string $layoutName Name of the layout to use. If none given, use "default"
303 * @return string Path and filename of layout file
304 * @throws Tx_Fluid_View_Exception_InvalidTemplateResourceException
305 * @author Sebastian Kurfürst <sebastian@typo3.org>
306 */
307 protected function resolveLayoutPathAndFilename($layoutName = 'default') {
308 if ($this->layoutPathAndFilename) {
309 return $this->layoutPathAndFilename;
310 }
311
312 $paths = $this->expandGenericPathPattern($this->layoutPathAndFilenamePattern, TRUE, TRUE);
313 foreach ($paths as &$path) {
314 $path = str_replace('@layout', $layoutName, $path);
315 if (file_exists($path)) {
316 return $path;
317 }
318 }
319 throw new Tx_Fluid_View_Exception_InvalidTemplateResourceException('The template files "' . implode('", "', $paths) . '" could not be loaded.', 1225709595);
320 }
321
322 /**
323 * Renders a partial.
324 *
325 * @param string $partialName
326 * @param string $sectionToRender
327 * @param array $variables
328 * @param Tx_Fluid_Core_ViewHelper_ViewHelperVariableContainer $viewHelperVariableContainer the View Helper Variable container to use.
329 * @return string
330 * @author Sebastian Kurfürst <sebastian@typo3.org>
331 * @author Bastian Waidelich <bastian@typo3.org>
332 * @author Robert Lemke <robert@typo3.org>
333 */
334 public function renderPartial($partialName, $sectionToRender, array $variables, $viewHelperVariableContainer = NULL) {
335 $partial = $this->parseTemplate($this->resolvePartialPathAndFilename($partialName));
336 $variableContainer = $this->objectManager->create('Tx_Fluid_Core_ViewHelper_TemplateVariableContainer', $variables);
337 $renderingContext = $this->buildRenderingContext($variableContainer);
338 if ($viewHelperVariableContainer !== NULL) {
339 $renderingContext->setViewHelperVariableContainer($viewHelperVariableContainer);
340 }
341 return $partial->render($renderingContext);
342 }
343
344 /**
345 * Figures out which partial to use.
346 *
347 * @param string $partialName The name of the partial
348 * @return string the full path which should be used. The path definitely exists.
349 * @throws Tx_Fluid_View_Exception_InvalidTemplateResourceException
350 * @author Sebastian Kurfürst <sebastian@typo3.org>
351 */
352 protected function resolvePartialPathAndFilename($partialName) {
353 $paths = $this->expandGenericPathPattern($this->partialPathAndFilenamePattern, TRUE, TRUE);
354 foreach ($paths as &$path) {
355 $path = str_replace('@partial', $partialName, $path);
356 if (file_exists($path)) {
357 return $path;
358 }
359 }
360 throw new Tx_Fluid_View_Exception_InvalidTemplateResourceException('The template files "' . implode('", "', $paths) . '" could not be loaded.', 1225709595);
361 }
362
363 /**
364 * Checks whether a template can be resolved for the current request context.
365 *
366 * @return boolean
367 * @author Karsten Dambekalns <karsten@typo3.org>
368 * @author Sebastian Kurfürst <sebastian@typo3.org>
369 * @api
370 */
371 public function hasTemplate() {
372 try {
373 $this->resolveTemplatePathAndFilename();
374 return TRUE;
375 } catch (Tx_Fluid_View_Exception_InvalidTemplateResourceException $e) {
376 return FALSE;
377 }
378 }
379
380 /**
381 * Parse the given template and return it.
382 *
383 * Will cache the results for one call.
384 *
385 * @param string $templatePathAndFilename absolute filename of the template to be parsed
386 * @return Tx_Fluid_Core_Parser_ParsedTemplateInterface the parsed template tree
387 * @throws Tx_Fluid_View_Exception_InvalidTemplateResourceException
388 * @author Sebastian Kurfürst <sebastian@typo3.org>
389 */
390 protected function parseTemplate($templatePathAndFilename) {
391 $templateSource = file_get_contents($templatePathAndFilename);
392 if ($templateSource === FALSE) {
393 throw new Tx_Fluid_View_Exception_InvalidTemplateResourceException('"' . $templatePathAndFilename . '" is not a valid template resource URI.', 1257246929);
394 }
395 return $this->templateParser->parse($templateSource);
396 }
397
398 /**
399 * Set the root path to the templates.
400 * If set, overrides the one determined from $this->templateRootPathPattern
401 *
402 * @param string $templateRootPath Root path to the templates. If set, overrides the one determined from $this->templateRootPathPattern
403 * @return void
404 * @author Sebastian Kurfürst <sebastian@typo3.org>
405 * @api
406 */
407 public function setTemplateRootPath($templateRootPath) {
408 $this->templateRootPath = $templateRootPath;
409 }
410
411 /**
412 * Resolves the template root to be used inside other paths.
413 *
414 * @return string Path to template root directory
415 * @author Sebastian Kurfürst <sebastian@typo3.org>
416 */
417 protected function getTemplateRootPath() {
418 if ($this->templateRootPath !== NULL) {
419 return $this->templateRootPath;
420 } else {
421 return str_replace('@packageResourcesPath', t3lib_extMgm::extPath($this->controllerContext->getRequest()->getControllerExtensionKey()) . 'Resources/', $this->templateRootPathPattern);
422 }
423 }
424
425 /**
426 * Set the root path to the partials.
427 * If set, overrides the one determined from $this->partialRootPathPattern
428 *
429 * @param string $partialRootPath Root path to the partials. If set, overrides the one determined from $this->partialRootPathPattern
430 * @return void
431 * @author Bastian Waidelich <bastian@typo3.org>
432 * @api
433 */
434 public function setPartialRootPath($partialRootPath) {
435 $this->partialRootPath = $partialRootPath;
436 }
437
438 /**
439 * Resolves the partial root to be used inside other paths.
440 *
441 * @return string Path to partial root directory
442 * @author Bastian Waidelich <bastian@typo3.org>
443 */
444 protected function getPartialRootPath() {
445 if ($this->partialRootPath !== NULL) {
446 return $this->partialRootPath;
447 } else {
448 return str_replace('@packageResourcesPath', t3lib_extMgm::extPath($this->controllerContext->getRequest()->getControllerExtensionKey()) . 'Resources/', $this->partialRootPathPattern);
449 }
450 }
451
452 /**
453 * Set the root path to the layouts.
454 * If set, overrides the one determined from $this->layoutRootPathPattern
455 *
456 * @param string $layoutRootPath Root path to the layouts. If set, overrides the one determined from $this->layoutRootPathPattern
457 * @return void
458 * @author Bastian Waidelich <bastian@typo3.org>
459 * @api
460 */
461 public function setLayoutRootPath($layoutRootPath) {
462 $this->layoutRootPath = $layoutRootPath;
463 }
464
465 /**
466 * Resolves the layout root to be used inside other paths.
467 *
468 * @return string Path to layout root directory
469 * @author Bastian Waidelich <bastian@typo3.org>
470 */
471 protected function getLayoutRootPath() {
472 if ($this->layoutRootPath !== NULL) {
473 return $this->layoutRootPath;
474 } else {
475 return str_replace('@packageResourcesPath', t3lib_extMgm::extPath($this->controllerContext->getRequest()->getControllerExtensionKey()) . 'Resources/', $this->layoutRootPathPattern);
476 }
477 }
478
479 /**
480 * Processes @templateRoot, @subpackage, @controller, and @format placeholders inside $pattern.
481 * This method is used to generate "fallback chains" for file system locations where a certain Partial can reside.
482 *
483 * If $bubbleControllerAndSubpackage is FALSE and $formatIsOptional is FALSE, then the resulting array will only have one element
484 * with all the above placeholders replaced.
485 *
486 * If you set $bubbleControllerAndSubpackage to TRUE, then you will get an array with potentially many elements:
487 * The first element of the array is like above. The second element has the @controller part set to "" (the empty string)
488 * The third element now has the @controller part again stripped off, and has the last subpackage part stripped off as well.
489 * This continues until both @subpackage and @controller are empty.
490 *
491 * Example for $bubbleControllerAndSubpackage is TRUE, we have the Tx_Fluid_MySubPackage_Controller_MyController as Controller Object Name and the current format is "html"
492 * If pattern is @templateRoot/@controller/@action.@format, then the resulting array is:
493 * - Resources/Private/Templates/MySubPackage/My/@action.html
494 * - Resources/Private/Templates/MySubPackage/@action.html
495 * - Resources/Private/Templates/@action.html
496 *
497 * If you set $formatIsOptional to TRUE, then for any of the above arrays, every element will be duplicated - once with @format
498 * replaced by the current request format, and once with .@format stripped off.
499 *
500 * @param string $pattern Pattern to be resolved
501 * @param boolean $bubbleControllerAndSubpackage if TRUE, then we successively split off parts from @controller and @subpackage until both are empty.
502 * @param boolean $formatIsOptional if TRUE, then half of the resulting strings will have .@format stripped off, and the other half will have it.
503 * @return array unix style path
504 * @author Sebastian Kurfürst <sebastian@typo3.org>
505 * @author Robert Lemke <robert@typo3.org>
506 */
507 protected function expandGenericPathPattern($pattern, $bubbleControllerAndSubpackage, $formatIsOptional) {
508 $pattern = str_replace('@templateRoot', $this->getTemplateRootPath(), $pattern);
509 $pattern = str_replace('@partialRoot', $this->getPartialRootPath(), $pattern);
510 $pattern = str_replace('@layoutRoot', $this->getLayoutRootPath(), $pattern);
511
512 $subPackageKey = '';
513 $controllerName = $this->controllerContext->getRequest()->getControllerName();
514
515 $subpackageParts = ($subPackageKey !== '') ? explode(Tx_Fluid_Fluid::NAMESPACE_SEPARATOR, $subPackageKey) : array();
516
517 $results = array();
518
519 $i = ($controllerName === NULL) ? 0 : -1;
520 do {
521 $temporaryPattern = $pattern;
522 if ($i < 0) {
523 $temporaryPattern = str_replace('@controller', $controllerName, $temporaryPattern);
524 } else {
525 $temporaryPattern = str_replace('//', '/', str_replace('@controller', '', $temporaryPattern));
526 }
527 $temporaryPattern = str_replace('@subpackage', implode('/', ($i<0 ? $subpackageParts : array_slice($subpackageParts, $i))), $temporaryPattern);
528
529 $results[] = t3lib_div::fixWindowsFilePath(str_replace('@format', $this->controllerContext->getRequest()->getFormat(), $temporaryPattern));
530 if ($formatIsOptional) {
531 $results[] = t3lib_div::fixWindowsFilePath(str_replace('.@format', '', $temporaryPattern));
532 }
533
534 } while($i++ < count($subpackageParts) && $bubbleControllerAndSubpackage);
535
536 return $results;
537 }
538 }
539
540 ?>