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