10cea29a6698ab8173f60c35112aa01350aa3bd2
[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 {
89 $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
90 /** @var ConfigurationManagerInterface $configurationManager */
91 $configurationManager = $this->objectManager->get(ConfigurationManagerInterface::class);
92 if ($contentObject === null) {
93 /** @var ContentObjectRenderer $contentObject */
94 $contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
95 }
96 $configurationManager->setContentObject($contentObject);
97 $this->templateParser = $this->objectManager->get(TemplateParser::class);
98 $this->setRenderingContext($this->objectManager->get(RenderingContext::class));
99 /** @var WebRequest $request */
100 $request = $this->objectManager->get(WebRequest::class);
101 $request->setRequestURI(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
102 $request->setBaseURI(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'));
103 /** @var UriBuilder $uriBuilder */
104 $uriBuilder = $this->objectManager->get(UriBuilder::class);
105 $uriBuilder->setRequest($request);
106 /** @var ControllerContext $controllerContext */
107 $controllerContext = $this->objectManager->get(ControllerContext::class);
108 $controllerContext->setRequest($request);
109 $controllerContext->setUriBuilder($uriBuilder);
110 $this->setControllerContext($controllerContext);
111 $this->templateCompiler = $this->objectManager->get(TemplateCompiler::class);
112 // singleton
113 $this->templateCompiler->setTemplateCache(GeneralUtility::makeInstance(CacheManager::class)->getCache('fluid_template'));
114 }
115
116 /**
117 * Sets the format of the current request (default format is "html")
118 *
119 * @param string $format
120 * @return void
121 * @api
122 */
123 public function setFormat($format)
124 {
125 $this->getRequest()->setFormat($format);
126 }
127
128 /**
129 * Returns the format of the current request (defaults is "html")
130 *
131 * @return string $format
132 * @api
133 */
134 public function getFormat()
135 {
136 return $this->getRequest()->getFormat();
137 }
138
139 /**
140 * Returns the current request object
141 *
142 * @return WebRequest
143 */
144 public function getRequest()
145 {
146 return $this->controllerContext->getRequest();
147 }
148
149 /**
150 * Sets the absolute path to a Fluid template file
151 *
152 * @param string $templatePathAndFilename Fluid template path
153 * @return void
154 * @api
155 */
156 public function setTemplatePathAndFilename($templatePathAndFilename)
157 {
158 $this->templatePathAndFilename = $templatePathAndFilename;
159 }
160
161 /**
162 * Returns the absolute path to a Fluid template file if it was specified with setTemplatePathAndFilename() before
163 *
164 * @return string Fluid template path
165 * @api
166 */
167 public function getTemplatePathAndFilename()
168 {
169 return $this->templatePathAndFilename;
170 }
171
172 /**
173 * Sets the Fluid template source
174 * You can use setTemplatePathAndFilename() alternatively if you only want to specify the template path
175 *
176 * @param string $templateSource Fluid template source code
177 * @return void
178 * @api
179 */
180 public function setTemplateSource($templateSource)
181 {
182 $this->templateSource = $templateSource;
183 }
184
185 /**
186 * Set the root path(s) to the templates.
187 *
188 * @param string[] $templateRootPaths Root paths to the templates.
189 * @return void
190 * @api
191 */
192 public function setTemplateRootPaths(array $templateRootPaths)
193 {
194 $this->templateRootPaths = $templateRootPaths;
195 }
196
197 /**
198 * Set template by name
199 * All set templateRootPaths are checked to find template by given name
200 *
201 * @param string $templateName Name of the template
202 * @throws InvalidTemplateResourceException
203 * @api
204 */
205 public function setTemplate($templateName)
206 {
207 if ($this->templateRootPaths === null) {
208 throw new InvalidTemplateResourceException('No template root path has been specified. Use setTemplateRootPaths().', 1430635895);
209 }
210 $format = $this->getRequest()->getFormat();
211 $templatePathAndFilename = null;
212 $possibleTemplatePaths = $this->buildListOfTemplateCandidates($templateName, $this->templateRootPaths, $format);
213 foreach ($possibleTemplatePaths as $possibleTemplatePath) {
214 if ($this->testFileExistence($possibleTemplatePath)) {
215 $templatePathAndFilename = $possibleTemplatePath;
216 break;
217 }
218 }
219 if ($templatePathAndFilename !== null) {
220 $this->setTemplatePathAndFilename($templatePathAndFilename);
221 } else {
222 throw new InvalidTemplateResourceException('Could not load template file. Tried following paths: "' . implode('", "', $possibleTemplatePaths) . '".', 1430635896);
223 }
224 }
225
226 /**
227 * Set the root path to the layouts.
228 *
229 * @param string $layoutRootPath Root path to the layouts.
230 * @return void
231 * @api
232 * @see setLayoutRootPaths()
233 * @deprecated since Fluid 7; Use setLayoutRootPaths() instead
234 */
235 public function setLayoutRootPath($layoutRootPath)
236 {
237 GeneralUtility::logDeprecatedFunction();
238 $this->setLayoutRootPaths(array($layoutRootPath));
239 }
240
241 /**
242 * Set the root path(s) to the layouts.
243 *
244 * @param string[] $layoutRootPaths Root path to the layouts
245 * @return void
246 * @api
247 */
248 public function setLayoutRootPaths(array $layoutRootPaths)
249 {
250 $this->layoutRootPaths = $layoutRootPaths;
251 }
252
253 /**
254 * Returns the first found entry in $this->layoutRootPaths.
255 * Don't use, this might not be the desired result.
256 *
257 * @throws InvalidTemplateResourceException
258 * @return string Path to layout root directory
259 * @deprecated since Fluid 7; Use getLayoutRootPaths() instead
260 */
261 public function getLayoutRootPath()
262 {
263 GeneralUtility::logDeprecatedFunction();
264 $layoutRootPaths = $this->getLayoutRootPaths();
265 return array_shift($layoutRootPaths);
266 }
267
268 /**
269 * Resolves the layout root to be used inside other paths.
270 *
271 * @return string Fluid layout root path
272 * @throws InvalidTemplateResourceException
273 * @api
274 */
275 public function getLayoutRootPaths()
276 {
277 if ($this->layoutRootPaths === null && $this->templatePathAndFilename === null) {
278 throw new InvalidTemplateResourceException('No layout root path has been specified. Use setLayoutRootPaths().', 1288091419);
279 }
280 if ($this->layoutRootPaths === null) {
281 $this->layoutRootPaths = array(dirname($this->templatePathAndFilename) . '/Layouts');
282 }
283 return $this->layoutRootPaths;
284 }
285
286 /**
287 * Set the root path to the partials.
288 * If set, overrides the one determined from $this->partialRootPathPattern
289 *
290 * @param string $partialRootPath Root path to the partials. If set, overrides the one determined from $this->partialRootPathPattern
291 * @return void
292 * @api
293 * @see setPartialRootPaths()
294 * @deprecated since Fluid 7; Use setPartialRootPaths() instead
295 */
296 public function setPartialRootPath($partialRootPath)
297 {
298 GeneralUtility::logDeprecatedFunction();
299 $this->setPartialRootPaths(array($partialRootPath));
300 }
301
302 /**
303 * Returns the first found entry in $this->partialRootPaths
304 * Don't use, this might not be the desired result.
305 *
306 * @throws InvalidTemplateResourceException
307 * @return string Path to partial root directory
308 * @deprecated since Fluid 7; Use getPartialRootPaths() instead
309 */
310 public function getPartialRootPath()
311 {
312 GeneralUtility::logDeprecatedFunction();
313 $partialRootPaths = $this->getPartialRootPaths();
314 return array_shift($partialRootPaths);
315 }
316
317 /**
318 * Set the root path(s) to the partials.
319 * If set, overrides the one determined from $this->partialRootPathPattern
320 *
321 * @param string[] $partialRootPaths Root paths to the partials. If set, overrides the one determined from $this->partialRootPathPattern
322 * @return void
323 * @api
324 */
325 public function setPartialRootPaths(array $partialRootPaths)
326 {
327 $this->partialRootPaths = $partialRootPaths;
328 }
329
330 /**
331 * Returns the absolute path to the folder that contains Fluid partial files
332 *
333 * @return string Fluid partial root path
334 * @throws InvalidTemplateResourceException
335 * @api
336 */
337 public function getPartialRootPaths()
338 {
339 if ($this->partialRootPaths === null && $this->templatePathAndFilename === null) {
340 throw new InvalidTemplateResourceException('No partial root path has been specified. Use setPartialRootPaths().', 1288094511);
341 }
342 if ($this->partialRootPaths === null) {
343 $this->partialRootPaths = array(dirname($this->templatePathAndFilename) . '/Partials');
344 }
345 return $this->partialRootPaths;
346 }
347
348 /**
349 * Checks whether a template can be resolved for the current request
350 *
351 * @return bool
352 * @api
353 */
354 public function hasTemplate()
355 {
356 try {
357 $this->getTemplateSource();
358 return true;
359 } catch (InvalidTemplateResourceException $e) {
360 return false;
361 }
362 }
363
364 /**
365 * Returns a unique identifier for the resolved template file
366 * This identifier is based on the template path and last modification date
367 *
368 * @param string $actionName Name of the action. This argument is not used in this view!
369 * @return string template identifier
370 * @throws InvalidTemplateResourceException
371 */
372 protected function getTemplateIdentifier($actionName = null)
373 {
374 if ($this->templateSource === null) {
375 $templatePathAndFilename = $this->getTemplatePathAndFilename();
376 $templatePathAndFilenameInfo = pathinfo($templatePathAndFilename);
377 $templateFilenameWithoutExtension = basename($templatePathAndFilename, '.' . $templatePathAndFilenameInfo['extension']);
378 $prefix = sprintf('template_file_%s', $templateFilenameWithoutExtension);
379 return $this->createIdentifierForFile($templatePathAndFilename, $prefix);
380 } else {
381 $templateSource = $this->getTemplateSource();
382 $prefix = 'template_source';
383 $templateIdentifier = sprintf('Standalone_%s_%s', $prefix, sha1($templateSource));
384 return $templateIdentifier;
385 }
386 }
387
388 /**
389 * Returns the Fluid template source code
390 *
391 * @param string $actionName Name of the action. This argument is not used in this view!
392 * @return string Fluid template source
393 * @throws InvalidTemplateResourceException
394 */
395 protected function getTemplateSource($actionName = null)
396 {
397 if ($this->templateSource === null && $this->templatePathAndFilename === null) {
398 throw new InvalidTemplateResourceException('No template has been specified. Use either setTemplateSource() or setTemplatePathAndFilename().', 1288085266);
399 }
400 if ($this->templateSource === null) {
401 if (!$this->testFileExistence($this->templatePathAndFilename)) {
402 throw new InvalidTemplateResourceException('Template could not be found at "' . $this->templatePathAndFilename . '".', 1288087061);
403 }
404 $this->templateSource = file_get_contents($this->templatePathAndFilename);
405 }
406 return $this->templateSource;
407 }
408
409 /**
410 * Returns a unique identifier for the resolved layout file.
411 * This identifier is based on the template path and last modification date
412 *
413 * @param string $layoutName The name of the layout
414 * @return string layout identifier
415 * @throws InvalidTemplateResourceException
416 */
417 protected function getLayoutIdentifier($layoutName = 'Default')
418 {
419 $layoutPathAndFilename = $this->getLayoutPathAndFilename($layoutName);
420 $prefix = 'layout_' . $layoutName;
421 return $this->createIdentifierForFile($layoutPathAndFilename, $prefix);
422 }
423
424 /**
425 * Resolves the path and file name of the layout file, based on
426 * $this->getLayoutRootPaths() and request format and returns the file contents
427 *
428 * @param string $layoutName Name of the layout to use. If none given, use "Default"
429 * @return string contents of the layout file if it was found
430 * @throws InvalidTemplateResourceException
431 */
432 protected function getLayoutSource($layoutName = 'Default')
433 {
434 $layoutPathAndFilename = $this->getLayoutPathAndFilename($layoutName);
435 $layoutSource = file_get_contents($layoutPathAndFilename);
436 if ($layoutSource === false) {
437 throw new InvalidTemplateResourceException('"' . $layoutPathAndFilename . '" is not a valid template resource URI.', 1312215888);
438 }
439 return $layoutSource;
440 }
441
442 /**
443 * Resolve the path and file name of the layout file, based on
444 * $this->getLayoutRootPaths() and request format
445 *
446 * In case a layout has already been set with setLayoutPathAndFilename(),
447 * this method returns that path, otherwise a path and filename will be
448 * resolved using the layoutPathAndFilenamePattern.
449 *
450 * @param string $layoutName Name of the layout to use. If none given, use "Default"
451 * @return string Path and filename of layout files
452 * @throws InvalidTemplateResourceException
453 */
454 protected function getLayoutPathAndFilename($layoutName = 'Default')
455 {
456 $possibleLayoutPaths = $this->buildListOfTemplateCandidates($layoutName, $this->getLayoutRootPaths(), $this->getRequest()->getFormat());
457 foreach ($possibleLayoutPaths as $layoutPathAndFilename) {
458 if ($this->testFileExistence($layoutPathAndFilename)) {
459 return $layoutPathAndFilename;
460 }
461 }
462
463 throw new InvalidTemplateResourceException('Could not load layout file. Tried following paths: "' . implode('", "', $possibleLayoutPaths) . '".', 1288092555);
464 }
465
466 /**
467 * Returns a unique identifier for the resolved partial file.
468 * This identifier is based on the template path and last modification date
469 *
470 * @param string $partialName The name of the partial
471 * @return string partial identifier
472 * @throws InvalidTemplateResourceException
473 */
474 protected function getPartialIdentifier($partialName)
475 {
476 $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
477 $prefix = 'partial_' . $partialName;
478 return $this->createIdentifierForFile($partialPathAndFilename, $prefix);
479 }
480
481 /**
482 * Resolves the path and file name of the partial file, based on
483 * $this->getPartialRootPath() and request format and returns the file contents
484 *
485 * @param string $partialName The name of the partial
486 * @return string contents of the layout file if it was found
487 * @throws InvalidTemplateResourceException
488 */
489 protected function getPartialSource($partialName)
490 {
491 $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
492 $partialSource = file_get_contents($partialPathAndFilename);
493 if ($partialSource === false) {
494 throw new InvalidTemplateResourceException('"' . $partialPathAndFilename . '" is not a valid template resource URI.', 1257246932);
495 }
496 return $partialSource;
497 }
498
499 /**
500 * Resolve the partial path and filename based on $this->getPartialRootPaths() and request format
501 *
502 * @param string $partialName The name of the partial
503 * @return string The full path which should be used. The path definitely exists.
504 * @throws InvalidTemplateResourceException
505 */
506 protected function getPartialPathAndFilename($partialName)
507 {
508 $possiblePartialPaths = $this->buildListOfTemplateCandidates($partialName, $this->getPartialRootPaths(), $this->getRequest()->getFormat());
509 foreach ($possiblePartialPaths as $partialPathAndFilename) {
510 if ($this->testFileExistence($partialPathAndFilename)) {
511 return $partialPathAndFilename;
512 }
513 }
514 throw new InvalidTemplateResourceException('Could not load partial file. Tried following paths: "' . implode('", "', $possiblePartialPaths) . '".', 1288092556);
515 }
516
517 /**
518 * Builds a list of possible candidates for a given template name
519 *
520 * @param string $templateName Name of the template to search for
521 * @param array $paths Paths to search in
522 * @param string $format The file format to use. e.g 'html' or 'txt'
523 * @return array Array of paths to search for the template file
524 */
525 protected function buildListOfTemplateCandidates($templateName, array $paths, $format)
526 {
527 $upperCasedTemplateName = $this->ucFileNameInPath($templateName);
528 $possibleTemplatePaths = array();
529 $paths = ArrayUtility::sortArrayWithIntegerKeys($paths);
530 $paths = array_reverse($paths, true);
531 foreach ($paths as $layoutRootPath) {
532 $possibleTemplatePaths[] = $this->resolveFileNamePath($layoutRootPath . '/' . $upperCasedTemplateName . '.' . $format);
533 $possibleTemplatePaths[] = $this->resolveFileNamePath($layoutRootPath . '/' . $upperCasedTemplateName);
534 if ($upperCasedTemplateName !== $templateName) {
535 $possibleTemplatePaths[] = $this->resolveFileNamePath($layoutRootPath . '/' . $templateName . '.' . $format);
536 $possibleTemplatePaths[] = $this->resolveFileNamePath($layoutRootPath . '/' . $templateName);
537 }
538 }
539 return $possibleTemplatePaths;
540 }
541
542 /**
543 * Returns a unique identifier for the given file in the format
544 * Standalone_<prefix>_<SHA1>
545 * The SH1 hash is a checksum that is based on the file path and last modification date
546 *
547 * @param string $pathAndFilename
548 * @param string $prefix
549 * @return string
550 */
551 protected function createIdentifierForFile($pathAndFilename, $prefix)
552 {
553 $templateModifiedTimestamp = filemtime($pathAndFilename);
554 $templateIdentifier = sprintf('Standalone_%s_%s', $prefix, sha1($pathAndFilename . '|' . $templateModifiedTimestamp));
555 $templateIdentifier = str_replace('/', '_', str_replace('.', '_', $templateIdentifier));
556 return $templateIdentifier;
557 }
558
559 /**
560 * Wrapper method to make the static call to GeneralUtility mockable in tests
561 *
562 * @param string $pathAndFilename
563 * @return string absolute pathAndFilename
564 */
565 protected function resolveFileNamePath($pathAndFilename)
566 {
567 return GeneralUtility::getFileAbsFileName(GeneralUtility::fixWindowsFilePath($pathAndFilename), false);
568 }
569 }