[BUGFIX] Prevent infinite loop in FAL access check
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / View / StandaloneView.php
1 <?php
2 namespace TYPO3\CMS\Fluid\View;
3
4 /** *
5 * This script is backported from the TYPO3 Flow package "TYPO3.Fluid". *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * *
12 * This script is distributed in the hope that it will be useful, but *
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
14 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
15 * General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU Lesser General Public *
18 * License along with the script. *
19 * If not, see http://www.gnu.org/licenses/lgpl.html *
20 * *
21 * The TYPO3 project - inspiring people to share! *
22 * */
23 use TYPO3\CMS\Core\Cache\CacheManager;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
26 use TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext;
27 use TYPO3\CMS\Extbase\Mvc\Web\Request as WebRequest;
28 use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
29 use TYPO3\CMS\Extbase\Object\ObjectManager;
30 use TYPO3\CMS\Extbase\Utility\ArrayUtility;
31 use TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler;
32 use TYPO3\CMS\Fluid\Core\Parser\TemplateParser;
33 use TYPO3\CMS\Fluid\Core\Rendering\RenderingContext;
34 use TYPO3\CMS\Fluid\View\Exception\InvalidTemplateResourceException;
35 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
36
37 /**
38 * A standalone template view.
39 * Should be used as view if you want to use Fluid without Extbase extensions
40 *
41 * @api
42 */
43 class StandaloneView extends AbstractTemplateView {
44
45 /**
46 * Source code of the Fluid template
47 *
48 * @var string
49 */
50 protected $templateSource = NULL;
51
52 /**
53 * absolute path of the Fluid template
54 *
55 * @var string
56 */
57 protected $templatePathAndFilename = NULL;
58
59 /**
60 * Path(s) to the template root
61 *
62 * @var string[]
63 */
64 protected $templateRootPaths = NULL;
65
66 /**
67 * Path(s) to the partial root
68 *
69 * @var string[]
70 */
71 protected $partialRootPaths = NULL;
72
73 /**
74 * Path(s) to the layout root
75 *
76 * @var string[]
77 */
78 protected $layoutRootPaths = NULL;
79
80 /**
81 * Constructor
82 *
83 * @param ContentObjectRenderer $contentObject The current cObject. If NULL a new instance will be created
84 * @throws \InvalidArgumentException
85 * @throws \UnexpectedValueException
86 */
87 public function __construct(ContentObjectRenderer $contentObject = NULL) {
88 $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
89 /** @var ConfigurationManagerInterface $configurationManager */
90 $configurationManager = $this->objectManager->get(ConfigurationManagerInterface::class);
91 if ($contentObject === NULL) {
92 /** @var ContentObjectRenderer $contentObject */
93 $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
94 }
95 $configurationManager->setContentObject($contentObject);
96 $this->templateParser = $this->objectManager->get(TemplateParser::class);
97 $this->setRenderingContext($this->objectManager->get(RenderingContext::class));
98 /** @var WebRequest $request */
99 $request = $this->objectManager->get(WebRequest::class);
100 $request->setRequestURI(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
101 $request->setBaseURI(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'));
102 /** @var UriBuilder $uriBuilder */
103 $uriBuilder = $this->objectManager->get(UriBuilder::class);
104 $uriBuilder->setRequest($request);
105 /** @var ControllerContext $controllerContext */
106 $controllerContext = $this->objectManager->get(ControllerContext::class);
107 $controllerContext->setRequest($request);
108 $controllerContext->setUriBuilder($uriBuilder);
109 $this->setControllerContext($controllerContext);
110 $this->templateCompiler = $this->objectManager->get(TemplateCompiler::class);
111 // singleton
112 $this->templateCompiler->setTemplateCache(GeneralUtility::makeInstance(CacheManager::class)->getCache('fluid_template'));
113 }
114
115 /**
116 * Sets the format of the current request (default format is "html")
117 *
118 * @param string $format
119 * @return void
120 * @api
121 */
122 public function setFormat($format) {
123 $this->getRequest()->setFormat($format);
124 }
125
126 /**
127 * Returns the format of the current request (defaults is "html")
128 *
129 * @return string $format
130 * @api
131 */
132 public function getFormat() {
133 return $this->getRequest()->getFormat();
134 }
135
136 /**
137 * Returns the current request object
138 *
139 * @return WebRequest
140 */
141 public function getRequest() {
142 return $this->controllerContext->getRequest();
143 }
144
145 /**
146 * Sets the absolute path to a Fluid template file
147 *
148 * @param string $templatePathAndFilename Fluid template path
149 * @return void
150 * @api
151 */
152 public function setTemplatePathAndFilename($templatePathAndFilename) {
153 $this->templatePathAndFilename = $templatePathAndFilename;
154 }
155
156 /**
157 * Returns the absolute path to a Fluid template file if it was specified with setTemplatePathAndFilename() before
158 *
159 * @return string Fluid template path
160 * @api
161 */
162 public function getTemplatePathAndFilename() {
163 return $this->templatePathAndFilename;
164 }
165
166 /**
167 * Sets the Fluid template source
168 * You can use setTemplatePathAndFilename() alternatively if you only want to specify the template path
169 *
170 * @param string $templateSource Fluid template source code
171 * @return void
172 * @api
173 */
174 public function setTemplateSource($templateSource) {
175 $this->templateSource = $templateSource;
176 }
177
178 /**
179 * Set the root path(s) to the templates.
180 *
181 * @param string[] $templateRootPaths Root paths to the templates.
182 * @return void
183 * @api
184 */
185 public function setTemplateRootPaths(array $templateRootPaths) {
186 $this->templateRootPaths = $templateRootPaths;
187 }
188
189 /**
190 * Set template by name
191 * All set templateRootPaths are checked to find template by given name
192 *
193 * @param string $templateName Name of the template
194 * @param bool $throwException
195 * @throws InvalidTemplateResourceException
196 * @api
197 */
198 public function setTemplate($templateName, $throwException = TRUE) {
199 if ($this->templateRootPaths === NULL) {
200 throw new InvalidTemplateResourceException('No template root path has been specified. Use setTemplateRootPaths().', 1430635895);
201 }
202 $format = $this->getRequest()->getFormat();
203 $templatePathAndFilename = NULL;
204 $possibleTemplatePaths = $this->buildListOfTemplateCandidates($templateName, $this->templateRootPaths, $format);
205 foreach ($possibleTemplatePaths as $possibleTemplatePath) {
206 if ($this->testFileExistence($possibleTemplatePath)) {
207 $templatePathAndFilename = $possibleTemplatePath;
208 break;
209 }
210 }
211 if ($templatePathAndFilename !== NULL) {
212 $this->setTemplatePathAndFilename($templatePathAndFilename);
213 } elseif ($throwException) {
214 throw new InvalidTemplateResourceException('Could not load template file. Tried following paths: "' . implode('", "', $possibleTemplatePaths) . '".', 1430635896);
215 }
216 }
217
218 /**
219 * Set the root path to the layouts.
220 *
221 * @param string $layoutRootPath Root path to the layouts.
222 * @return void
223 * @api
224 * @see setLayoutRootPaths()
225 * @deprecated since Fluid 7; Use setLayoutRootPaths() instead
226 */
227 public function setLayoutRootPath($layoutRootPath) {
228 GeneralUtility::logDeprecatedFunction();
229 $this->setLayoutRootPaths(array($layoutRootPath));
230 }
231
232 /**
233 * Set the root path(s) to the layouts.
234 *
235 * @param string[] $layoutRootPaths Root path to the layouts
236 * @return void
237 * @api
238 */
239 public function setLayoutRootPaths(array $layoutRootPaths) {
240 $this->layoutRootPaths = $layoutRootPaths;
241 }
242
243 /**
244 * Returns the first found entry in $this->layoutRootPaths.
245 * Don't use, this might not be the desired result.
246 *
247 * @throws InvalidTemplateResourceException
248 * @return string Path to layout root directory
249 * @deprecated since Fluid 7; Use getLayoutRootPaths() instead
250 */
251 public function getLayoutRootPath() {
252 GeneralUtility::logDeprecatedFunction();
253 $layoutRootPaths = $this->getLayoutRootPaths();
254 return array_shift($layoutRootPaths);
255 }
256
257 /**
258 * Resolves the layout root to be used inside other paths.
259 *
260 * @return string Fluid layout root path
261 * @throws InvalidTemplateResourceException
262 * @api
263 */
264 public function getLayoutRootPaths() {
265 if ($this->layoutRootPaths === NULL && $this->templatePathAndFilename === NULL) {
266 throw new InvalidTemplateResourceException('No layout root path has been specified. Use setLayoutRootPaths().', 1288091419);
267 }
268 if ($this->layoutRootPaths === NULL) {
269 $this->layoutRootPaths = array(dirname($this->templatePathAndFilename) . '/Layouts');
270 }
271 return $this->layoutRootPaths;
272 }
273
274 /**
275 * Set the root path to the partials.
276 * If set, overrides the one determined from $this->partialRootPathPattern
277 *
278 * @param string $partialRootPath Root path to the partials. If set, overrides the one determined from $this->partialRootPathPattern
279 * @return void
280 * @api
281 * @see setPartialRootPaths()
282 * @deprecated since Fluid 7; Use setPartialRootPaths() instead
283 */
284 public function setPartialRootPath($partialRootPath) {
285 GeneralUtility::logDeprecatedFunction();
286 $this->setPartialRootPaths(array($partialRootPath));
287 }
288
289 /**
290 * Returns the first found entry in $this->partialRootPaths
291 * Don't use, this might not be the desired result.
292 *
293 * @throws InvalidTemplateResourceException
294 * @return string Path to partial root directory
295 * @deprecated since Fluid 7; Use getPartialRootPaths() instead
296 */
297 public function getPartialRootPath() {
298 GeneralUtility::logDeprecatedFunction();
299 $partialRootPaths = $this->getPartialRootPaths();
300 return array_shift($partialRootPaths);
301 }
302
303 /**
304 * Set the root path(s) to the partials.
305 * If set, overrides the one determined from $this->partialRootPathPattern
306 *
307 * @param string[] $partialRootPaths Root paths to the partials. If set, overrides the one determined from $this->partialRootPathPattern
308 * @return void
309 * @api
310 */
311 public function setPartialRootPaths(array $partialRootPaths) {
312 $this->partialRootPaths = $partialRootPaths;
313 }
314
315 /**
316 * Returns the absolute path to the folder that contains Fluid partial files
317 *
318 * @return string Fluid partial root path
319 * @throws InvalidTemplateResourceException
320 * @api
321 */
322 public function getPartialRootPaths() {
323 if ($this->partialRootPaths === NULL && $this->templatePathAndFilename === NULL) {
324 throw new InvalidTemplateResourceException('No partial root path has been specified. Use setPartialRootPaths().', 1288094511);
325 }
326 if ($this->partialRootPaths === NULL) {
327 $this->partialRootPaths = array(dirname($this->templatePathAndFilename) . '/Partials');
328 }
329 return $this->partialRootPaths;
330 }
331
332 /**
333 * Checks whether a template can be resolved for the current request
334 *
335 * @return bool
336 * @api
337 */
338 public function hasTemplate() {
339 try {
340 $this->getTemplateSource();
341 return TRUE;
342 } catch (InvalidTemplateResourceException $e) {
343 return FALSE;
344 }
345 }
346
347 /**
348 * Returns a unique identifier for the resolved template file
349 * This identifier is based on the template path and last modification date
350 *
351 * @param string $actionName Name of the action. This argument is not used in this view!
352 * @return string template identifier
353 * @throws InvalidTemplateResourceException
354 */
355 protected function getTemplateIdentifier($actionName = NULL) {
356 if ($this->templateSource === NULL) {
357 $templatePathAndFilename = $this->getTemplatePathAndFilename();
358 $templatePathAndFilenameInfo = pathinfo($templatePathAndFilename);
359 $templateFilenameWithoutExtension = basename($templatePathAndFilename, '.' . $templatePathAndFilenameInfo['extension']);
360 $prefix = sprintf('template_file_%s', $templateFilenameWithoutExtension);
361 return $this->createIdentifierForFile($templatePathAndFilename, $prefix);
362 } else {
363 $templateSource = $this->getTemplateSource();
364 $prefix = 'template_source';
365 $templateIdentifier = sprintf('Standalone_%s_%s', $prefix, sha1($templateSource));
366 return $templateIdentifier;
367 }
368 }
369
370 /**
371 * Returns the Fluid template source code
372 *
373 * @param string $actionName Name of the action. This argument is not used in this view!
374 * @return string Fluid template source
375 * @throws InvalidTemplateResourceException
376 */
377 protected function getTemplateSource($actionName = NULL) {
378 if ($this->templateSource === NULL && $this->templatePathAndFilename === NULL) {
379 throw new InvalidTemplateResourceException('No template has been specified. Use either setTemplateSource() or setTemplatePathAndFilename().', 1288085266);
380 }
381 if ($this->templateSource === NULL) {
382 if (!$this->testFileExistence($this->templatePathAndFilename)) {
383 throw new InvalidTemplateResourceException('Template could not be found at "' . $this->templatePathAndFilename . '".', 1288087061);
384 }
385 $this->templateSource = file_get_contents($this->templatePathAndFilename);
386 }
387 return $this->templateSource;
388 }
389
390 /**
391 * Returns a unique identifier for the resolved layout file.
392 * This identifier is based on the template path and last modification date
393 *
394 * @param string $layoutName The name of the layout
395 * @return string layout identifier
396 * @throws InvalidTemplateResourceException
397 */
398 protected function getLayoutIdentifier($layoutName = 'Default') {
399 $layoutPathAndFilename = $this->getLayoutPathAndFilename($layoutName);
400 $prefix = 'layout_' . $layoutName;
401 return $this->createIdentifierForFile($layoutPathAndFilename, $prefix);
402 }
403
404 /**
405 * Resolves the path and file name of the layout file, based on
406 * $this->getLayoutRootPaths() and request format and returns the file contents
407 *
408 * @param string $layoutName Name of the layout to use. If none given, use "Default"
409 * @return string contents of the layout file if it was found
410 * @throws InvalidTemplateResourceException
411 */
412 protected function getLayoutSource($layoutName = 'Default') {
413 $layoutPathAndFilename = $this->getLayoutPathAndFilename($layoutName);
414 $layoutSource = file_get_contents($layoutPathAndFilename);
415 if ($layoutSource === FALSE) {
416 throw new InvalidTemplateResourceException('"' . $layoutPathAndFilename . '" is not a valid template resource URI.', 1312215888);
417 }
418 return $layoutSource;
419 }
420
421 /**
422 * Resolve the path and file name of the layout file, based on
423 * $this->getLayoutRootPaths() and request format
424 *
425 * In case a layout has already been set with setLayoutPathAndFilename(),
426 * this method returns that path, otherwise a path and filename will be
427 * resolved using the layoutPathAndFilenamePattern.
428 *
429 * @param string $layoutName Name of the layout to use. If none given, use "Default"
430 * @return string Path and filename of layout files
431 * @throws InvalidTemplateResourceException
432 */
433 protected function getLayoutPathAndFilename($layoutName = 'Default') {
434 $possibleLayoutPaths = $this->buildListOfTemplateCandidates($layoutName, $this->getLayoutRootPaths(), $this->getRequest()->getFormat());
435 foreach ($possibleLayoutPaths as $layoutPathAndFilename) {
436 if ($this->testFileExistence($layoutPathAndFilename)) {
437 return $layoutPathAndFilename;
438 }
439 }
440
441 throw new InvalidTemplateResourceException('Could not load layout file. Tried following paths: "' . implode('", "', $possibleLayoutPaths) . '".', 1288092555);
442 }
443
444 /**
445 * Returns a unique identifier for the resolved partial file.
446 * This identifier is based on the template path and last modification date
447 *
448 * @param string $partialName The name of the partial
449 * @return string partial identifier
450 * @throws InvalidTemplateResourceException
451 */
452 protected function getPartialIdentifier($partialName) {
453 $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
454 $prefix = 'partial_' . $partialName;
455 return $this->createIdentifierForFile($partialPathAndFilename, $prefix);
456 }
457
458 /**
459 * Resolves the path and file name of the partial file, based on
460 * $this->getPartialRootPath() and request format and returns the file contents
461 *
462 * @param string $partialName The name of the partial
463 * @return string contents of the layout file if it was found
464 * @throws InvalidTemplateResourceException
465 */
466 protected function getPartialSource($partialName) {
467 $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
468 $partialSource = file_get_contents($partialPathAndFilename);
469 if ($partialSource === FALSE) {
470 throw new InvalidTemplateResourceException('"' . $partialPathAndFilename . '" is not a valid template resource URI.', 1257246932);
471 }
472 return $partialSource;
473 }
474
475 /**
476 * Resolve the partial path and filename based on $this->getPartialRootPaths() and request format
477 *
478 * @param string $partialName The name of the partial
479 * @return string The full path which should be used. The path definitely exists.
480 * @throws InvalidTemplateResourceException
481 */
482 protected function getPartialPathAndFilename($partialName) {
483 $possiblePartialPaths = $this->buildListOfTemplateCandidates($partialName, $this->getPartialRootPaths(), $this->getRequest()->getFormat());
484 foreach ($possiblePartialPaths as $partialPathAndFilename) {
485 if ($this->testFileExistence($partialPathAndFilename)) {
486 return $partialPathAndFilename;
487 }
488 }
489 throw new InvalidTemplateResourceException('Could not load partial file. Tried following paths: "' . implode('", "', $possiblePartialPaths) . '".', 1288092556);
490 }
491
492 /**
493 * Builds a list of possible candidates for a given template name
494 *
495 * @param string $templateName Name of the template to search for
496 * @param array $paths Paths to search in
497 * @param string $format The file format to use. e.g 'html' or 'txt'
498 * @return array Array of paths to search for the template file
499 */
500 protected function buildListOfTemplateCandidates($templateName, array $paths, $format) {
501 $upperCasedTemplateName = $this->ucFileNameInPath($templateName);
502 $possibleTemplatePaths = array();
503 $paths = ArrayUtility::sortArrayWithIntegerKeys($paths);
504 $paths = array_reverse($paths, TRUE);
505 foreach ($paths as $layoutRootPath) {
506 $possibleTemplatePaths[] = $this->resolveFileNamePath($layoutRootPath . '/' . $upperCasedTemplateName . '.' . $format);
507 $possibleTemplatePaths[] = $this->resolveFileNamePath($layoutRootPath . '/' . $upperCasedTemplateName);
508 if ($upperCasedTemplateName !== $templateName) {
509 $possibleTemplatePaths[] = $this->resolveFileNamePath($layoutRootPath . '/' . $templateName . '.' . $format);
510 $possibleTemplatePaths[] = $this->resolveFileNamePath($layoutRootPath . '/' . $templateName);
511 }
512 }
513 return $possibleTemplatePaths;
514 }
515
516 /**
517 * Returns a unique identifier for the given file in the format
518 * Standalone_<prefix>_<SHA1>
519 * The SH1 hash is a checksum that is based on the file path and last modification date
520 *
521 * @param string $pathAndFilename
522 * @param string $prefix
523 * @return string
524 */
525 protected function createIdentifierForFile($pathAndFilename, $prefix) {
526 $templateModifiedTimestamp = filemtime($pathAndFilename);
527 $templateIdentifier = sprintf('Standalone_%s_%s', $prefix, sha1($pathAndFilename . '|' . $templateModifiedTimestamp));
528 $templateIdentifier = str_replace('/', '_', str_replace('.', '_', $templateIdentifier));
529 return $templateIdentifier;
530 }
531
532 /**
533 * Wrapper method to make the static call to GeneralUtility mockable in tests
534 *
535 * @param string $pathAndFilename
536 * @return string absolute pathAndFilename
537 */
538 protected function resolveFileNamePath($pathAndFilename) {
539 return GeneralUtility::getFileAbsFileName(GeneralUtility::fixWindowsFilePath($pathAndFilename), FALSE);
540 }
541
542 }