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