Completed
Pull Request — master (#470)
by Claus
01:35
created

TemplatePaths::getFormat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
namespace TYPO3Fluid\Fluid\View;
4
5
/*
6
 * This file belongs to the package "TYPO3 Fluid".
7
 * See LICENSE.txt that was shipped with this package.
8
 */
9
10
use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException;
11
12
/**
13
 * Template Paths Holder
14
 *
15
 * Class used to hold and resolve template files
16
 * and paths in multiple supported ways.
17
 *
18
 * The purpose of this class is to homogenise the
19
 * API that is used when working with template
20
 * paths coming from TypoScript, as well as serve
21
 * as a way to quickly generate default template-,
22
 * layout- and partial root paths by package.
23
 *
24
 * The constructor accepts two different types of
25
 * input - anything not of those types is silently
26
 * ignored:
27
 *
28
 * - a `string` input is assumed a package name
29
 *   and will call the `fillDefaultsByPackageName`
30
 *   value filling method.
31
 * - an `array` input is assumed a TypoScript-style
32
 *   array of root paths in one or more of the
33
 *   supported structures and will call the
34
 *   `fillFromTypoScriptArray` method.
35
 *
36
 * Either method can also be called after instance
37
 * is created, but both will overwrite any paths
38
 * you have previously configured.
39
 *
40
 * DEPRECATION INFORMATION
41
 * -----------------------
42
 *
43
 * This class and the entire "View" scope with it are deprecated in
44
 * Fluid 3.0 and will be removed in version 4.0. The new approach
45
 * to template file usage in Fluid is condensed into a single
46
 * replacement feature: Atoms.
47
 *
48
 * Atoms serve the same purposes as Templates, Partials and Layouts
49
 * did in earlier versions (and have a compatibility layer that
50
 * will exist throughout the 3.x lifetime) but provide a unified
51
 * collection that's also associated with a proper namespace and
52
 * allows the Atoms to work in the same way a ViewHelper works;
53
 * by supporting arguments, descriptions, examples and so on but
54
 * not being based on any specific PHP class. In essence a template
55
 * file becomes a pseudo ViewHelper that renders what's contained
56
 * in the template file instead of calling a PHP method.
57
 *
58
 * @deprecated Will be removed in Fluid 4.0
59
 */
60
class TemplatePaths
61
{
62
63
    const DEFAULT_FORMAT = 'html';
64
    const DEFAULT_TEMPLATES_DIRECTORY = 'Resources/Private/Templates/';
65
    const DEFAULT_LAYOUTS_DIRECTORY = 'Resources/Private/Layouts/';
66
    const DEFAULT_PARTIALS_DIRECTORY = 'Resources/Private/Partials/';
67
    const CONFIG_TEMPLATEROOTPATHS = 'templateRootPaths';
68
    const CONFIG_LAYOUTROOTPATHS = 'layoutRootPaths';
69
    const CONFIG_PARTIALROOTPATHS = 'partialRootPaths';
70
    const CONFIG_FORMAT = 'format';
71
    const NAME_TEMPLATES = 'templates';
72
    const NAME_LAYOUTS = 'layouts';
73
    const NAME_PARTIALS = 'partials';
74
75
    /**
76
     * Holds already resolved identifiers for template files
77
     *
78
     * @var array
79
     */
80
    protected $resolvedIdentifiers = [
81
        self::NAME_TEMPLATES => [],
82
        self::NAME_LAYOUTS => [],
83
        self::NAME_PARTIALS => []
84
    ];
85
86
    /**
87
     * Holds already resolved identifiers for template files
88
     *
89
     * @var array
90
     */
91
    protected $resolvedFiles = [
92
        self::NAME_TEMPLATES => [],
93
        self::NAME_LAYOUTS => [],
94
        self::NAME_PARTIALS => []
95
    ];
96
97
    /**
98
     * @var array
99
     */
100
    protected $templateRootPaths = [];
101
102
    /**
103
     * @var array
104
     */
105
    protected $layoutRootPaths = [];
106
107
    /**
108
     * @var array
109
     */
110
    protected $partialRootPaths = [];
111
112
    /**
113
     * @var string
114
     */
115
    protected $templatePathAndFilename = null;
116
117
    /**
118
     * @var string
119
     */
120
    protected $layoutPathAndFilename = null;
121
122
    /**
123
     * @var string|NULL
124
     */
125
    protected $templateSource = null;
126
127
    /**
128
     * @var string
129
     */
130
    protected $format = self::DEFAULT_FORMAT;
131
132
    /**
133
     * @param array|string|NULL $packageNameOrArray
134
     */
135
    public function __construct($packageNameOrArray = null)
136
    {
137
        if (is_array($packageNameOrArray)) {
138
            $this->fillFromConfigurationArray($packageNameOrArray);
139
        } elseif (!empty($packageNameOrArray)) {
140
            $this->fillDefaultsByPackageName($packageNameOrArray);
141
        }
142
    }
143
144
    /**
145
     * @return array
146
     */
147
    public function toArray(): array
148
    {
149
        return [
150
            self::CONFIG_TEMPLATEROOTPATHS => $this->sanitizePaths($this->getTemplateRootPaths()),
151
            self::CONFIG_LAYOUTROOTPATHS => $this->sanitizePaths($this->getLayoutRootPaths()),
152
            self::CONFIG_PARTIALROOTPATHS => $this->sanitizePaths($this->getPartialRootPaths())
153
        ];
154
    }
155
156
    /**
157
     * @param string|null $templatePathAndFilename
158
     * @return void
159
     */
160
    public function setTemplatePathAndFilename($templatePathAndFilename)
161
    {
162
        $this->templatePathAndFilename = $templatePathAndFilename === null ? null : (string) $this->sanitizePath($templatePathAndFilename);
163
    }
164
165
    /**
166
     * @param string $layoutPathAndFilename
167
     * @return void
168
     */
169
    public function setLayoutPathAndFilename($layoutPathAndFilename)
170
    {
171
        $this->layoutPathAndFilename = (string) $this->sanitizePath($layoutPathAndFilename);
172
    }
173
174
    /**
175
     * @return array
176
     */
177
    public function getTemplateRootPaths()
178
    {
179
        return $this->templateRootPaths;
180
    }
181
182
    /**
183
     * @param array $templateRootPaths
184
     * @return void
185
     */
186
    public function setTemplateRootPaths(array $templateRootPaths)
187
    {
188
        $this->templateRootPaths = (array) $this->sanitizePaths($templateRootPaths);
189
        $this->clearResolvedIdentifiersAndTemplates(self::NAME_TEMPLATES);
190
    }
191
192
    /**
193
     * @return array
194
     */
195
    public function getLayoutRootPaths()
196
    {
197
        return $this->layoutRootPaths;
198
    }
199
200
    /**
201
     * @param array $layoutRootPaths
202
     * @return void
203
     */
204
    public function setLayoutRootPaths(array $layoutRootPaths)
205
    {
206
        $this->layoutRootPaths = (array) $this->sanitizePaths($layoutRootPaths);
207
        $this->clearResolvedIdentifiersAndTemplates(self::NAME_LAYOUTS);
208
    }
209
210
    /**
211
     * @return array
212
     */
213
    public function getPartialRootPaths(): array
214
    {
215
        return $this->partialRootPaths;
216
    }
217
218
    /**
219
     * @param array $partialRootPaths
220
     * @return void
221
     */
222
    public function setPartialRootPaths(array $partialRootPaths)
223
    {
224
        $this->partialRootPaths = (array) $this->sanitizePaths($partialRootPaths);
225
        $this->clearResolvedIdentifiersAndTemplates(self::NAME_PARTIALS);
226
    }
227
228
    /**
229
     * @return string
230
     */
231
    public function getFormat()
232
    {
233
        return $this->format;
234
    }
235
236
    /**
237
     * @param string $format
238
     * @return void
239
     */
240
    public function setFormat(string $format)
241
    {
242
        $this->format = $format;
243
    }
244
245
    /**
246
     * Attempts to resolve an absolute filename
247
     * of a template (i.e. `templateRootPaths`)
248
     * using a controller name, action and format.
249
     *
250
     * Works _backwards_ through template paths in
251
     * order to achieve an "overlay"-type behavior
252
     * where the last paths added are the first to
253
     * be checked and the first path added acts as
254
     * fallback if no other paths have the file.
255
     *
256
     * If the file does not exist in any path,
257
     * including fallback path, `NULL` is returned.
258
     *
259
     * Path configurations filled from TypoScript
260
     * is automatically recorded in the right
261
     * order (see `fillFromTypoScriptArray`), but
262
     * when manually setting the paths that should
263
     * be checked, you as user must be aware of
264
     * this reverse behavior (which you should
265
     * already be, given that it is the same way
266
     * TypoScript path configurations work).
267
     *
268
     * @param string $controller
269
     * @param string $action
270
     * @param string|null $format
271
     * @return string|null
272
     */
273
    public function resolveTemplateFileForControllerAndActionAndFormat(string $controller, string $action, ?string $format = null): ?string
274
    {
275
        if ($this->templatePathAndFilename !== null) {
276
            return $this->templatePathAndFilename;
277
        }
278
        $format = $format ?: $this->getFormat();
279
        $controller = str_replace('\\', '/', $controller);
280
        $action = ucfirst($action);
281
        $identifier = $controller . '/' . $action . '.' . $format;
282
        if (!array_key_exists($identifier, $this->resolvedFiles['templates'])) {
283
            $templateRootPaths = $this->getTemplateRootPaths();
284
            foreach ([$controller . '/' . $action, $action] as $possibleRelativePath) {
285
                try {
286
                    return $this->resolvedFiles['templates'][$identifier] = $this->resolveFileInPaths($templateRootPaths, $possibleRelativePath, $format);
287
                } catch (InvalidTemplateResourceException $error) {
288
                    $this->resolvedFiles['templates'][$identifier] = null;
289
                }
290
            }
291
        }
292
        return isset($this->resolvedFiles[self::NAME_TEMPLATES][$identifier]) ? $this->resolvedFiles[self::NAME_TEMPLATES][$identifier] : null;
293
    }
294
295
    /**
296
     * @param string|NULL $controllerName
297
     * @param string $format
298
     * @return array
299
     */
300
    public function resolveAvailableTemplateFiles(?string $controllerName, string $format = null): array
301
    {
302
        $paths = $this->getTemplateRootPaths();
303
        foreach ($paths as $index => $path) {
304
            $paths[$index] = rtrim($path . $controllerName, '/') . '/';
305
        }
306
        return $this->resolveFilesInFolders($paths, $format ?: $this->getFormat());
307
    }
308
309
    /**
310
     * @param string $format
311
     * @return array
312
     */
313
    public function resolveAvailablePartialFiles(string $format = null): array
314
    {
315
        return $this->resolveFilesInFolders($this->getPartialRootPaths(), $format ?: $this->getFormat());
316
    }
317
318
    /**
319
     * @param string $format
320
     * @return array
321
     */
322
    public function resolveAvailableLayoutFiles(string $format = null): array
323
    {
324
        return $this->resolveFilesInFolders($this->getLayoutRootPaths(), $format ?: $this->getFormat());
325
    }
326
327
    /**
328
     * @param array $folders
329
     * @param string $format
330
     * @return array
331
     */
332
    protected function resolveFilesInFolders(array $folders, string $format): array
333
    {
334
        $files = [];
335
        foreach ($folders as $folder) {
336
            $files = array_merge($files, $this->resolveFilesInFolder($folder, $format));
337
        }
338
        return array_values($files);
339
    }
340
341
    /**
342
     * @param string $folder
343
     * @param string $format
344
     * @return array
345
     */
346
    protected function resolveFilesInFolder(string $folder, string $format): array
347
    {
348
        if (!is_dir($folder)) {
349
            return [];
350
        }
351
352
        $directoryIterator = new \RecursiveDirectoryIterator($folder, \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::SKIP_DOTS);
353
        $recursiveIterator = new \RecursiveIteratorIterator($directoryIterator, \RecursiveIteratorIterator::SELF_FIRST);
354
        $filterIterator = new \CallbackFilterIterator($recursiveIterator, function($current, $key, $iterator) use ($format): bool {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $iterator is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
355
            return $current->getExtension() === $format;
356
        });
357
358
        return array_keys(iterator_to_array($filterIterator));
359
    }
360
361
    /**
362
     * Fills path arrays based on a traditional
363
     * TypoScript array which may contain one or
364
     * more of the supported structures, in order
365
     * of priority:
366
     *
367
     * - `plugin.tx_yourext.view.templateRootPath` and siblings.
368
     * - `plugin.tx_yourext.view.templateRootPaths` and siblings.
369
     * - `plugin.tx_yourext.view.overlays.otherextension.templateRootPath` and siblings.
370
     *
371
     * The paths are treated as follows, using the
372
     * `template`-type paths as an example:
373
     *
374
     * - If `templateRootPath` is defined, it gets
375
     *   used as the _first_ path in the internal
376
     *   paths array.
377
     * - If `templateRootPaths` is defined, all
378
     *   values from it are _appended_ to the
379
     *   internal paths array.
380
     * - If `overlays.*` exists in the array it is
381
     *   iterated, each `templateRootPath` entry
382
     *   from it _appended_ to the internal array.
383
     *
384
     * The result is that after filling, the path
385
     * arrays will contain one or more entries in
386
     * the order described above, depending on how
387
     * many of the possible configurations were
388
     * present in the input array.
389
     *
390
     * Will replace any currently configured paths.
391
     *
392
     * @param array $paths
393
     * @return void
394
     */
395
    public function fillFromConfigurationArray($paths)
396
    {
397
        list ($templateRootPaths, $layoutRootPaths, $partialRootPaths, $format) = $this->extractPathArrays($paths);
398
        $this->setTemplateRootPaths($templateRootPaths);
399
        $this->setLayoutRootPaths($layoutRootPaths);
400
        $this->setPartialRootPaths($partialRootPaths);
401
        $this->setFormat($format);
402
    }
403
404
    /**
405
     * Fills path arrays with default expected paths
406
     * based on package name (converted to extension
407
     * key automatically).
408
     *
409
     * Will replace any currently configured paths.
410
     *
411
     * @param string $packageName
412
     * @return void
413
     */
414
    public function fillDefaultsByPackageName($packageName)
415
    {
416
        $path = $this->getPackagePath($packageName);
417
        $this->setTemplateRootPaths([$path . self::DEFAULT_TEMPLATES_DIRECTORY]);
418
        $this->setLayoutRootPaths([$path . self::DEFAULT_LAYOUTS_DIRECTORY]);
419
        $this->setPartialRootPaths([$path . self::DEFAULT_PARTIALS_DIRECTORY]);
420
    }
421
422
    /**
423
     * Sanitize a path, ensuring it is absolute and
424
     * if a directory, suffixed by a trailing slash.
425
     *
426
     * @param string|array $path
427
     * @return string|string[]
428
     */
429
    protected function sanitizePath($path)
430
    {
431
        if (is_array($path)) {
432
            $paths = array_map([$this, 'sanitizePath'], $path);
433
            return array_unique($paths);
434
        } elseif (is_string($path) && ($wrapper = parse_url($path, PHP_URL_SCHEME)) && in_array($wrapper, stream_get_wrappers(), true)) {
435
            return $path;
436
        } elseif (!empty($path)) {
437
            $path = str_replace(['\\', '//'], '/', (string) $path);
438
            $path = (string) $this->ensureAbsolutePath($path);
439
            if (is_dir($path)) {
440
                $path = $this->ensureSuffixedPath($path);
441
            }
442
        }
443
        return $path;
444
    }
445
446
    /**
447
     * Sanitize paths passing each through sanitizePath().
448
     *
449
     * @param array $paths
450
     * @return array
451
     */
452
    protected function sanitizePaths(array $paths)
453
    {
454
        return array_unique(array_map([$this, 'sanitizePath'], $paths));
455
    }
456
457
    /**
458
     * Guarantees that $reference is turned into a
459
     * correct, absolute path.
460
     *
461
     * @param string $path
462
     * @return string
463
     */
464
    protected function ensureAbsolutePath(string $path)
465
    {
466
        return ((!empty($path) && $path{0} !== '/' && $path{1} !== ':') ? $this->sanitizePath(realpath($path)) : $path);
467
    }
468
469
    /**
470
     * Guarantees that array $reference with paths
471
     * are turned into correct, absolute paths
472
     *
473
     * @param array $reference
474
     * @return array
475
     */
476
    protected function ensureAbsolutePaths(array $reference): array
477
    {
478
        return array_map([$this, 'ensureAbsolutePath'], $reference);
479
    }
480
481
    /**
482
     * @param string $path
483
     * @return string
484
     */
485
    protected function ensureSuffixedPath(string $path): string
486
    {
487
        return $path !== '' ? rtrim($path, '/') . '/' : '';
488
    }
489
490
    /**
491
     * Extract an array of three arrays of paths, one
492
     * for each of the types of Fluid file resources.
493
     * Accepts one or both of the singular and plural
494
     * path definitions in the input - returns the
495
     * combined collections of paths based on both
496
     * the singular and plural entries with the singular
497
     * entries being recorded first and plurals second.
498
     *
499
     * Adds legacy singular name as last option, if set.
500
     *
501
     * @param array $paths
502
     * @return array
503
     */
504
    protected function extractPathArrays(array $paths): array
505
    {
506
        $format = $this->getFormat();
507
        // pre-processing: if special parameters exist, extract them:
508
        if (isset($paths[self::CONFIG_FORMAT])) {
509
            $format = $paths[self::CONFIG_FORMAT];
510
        }
511
        $pathParts = [
512
            self::CONFIG_TEMPLATEROOTPATHS,
513
            self::CONFIG_LAYOUTROOTPATHS,
514
            self::CONFIG_PARTIALROOTPATHS
515
        ];
516
        $pathCollections = [];
517
        foreach ($pathParts as $pathPart) {
518
            $partPaths = [];
519
            if (isset($paths[$pathPart]) && is_array($paths[$pathPart])) {
520
                $partPaths = array_merge($partPaths, $paths[$pathPart]);
521
            }
522
            $pathCollections[] = array_unique(array_map([$this, 'ensureSuffixedPath'], $partPaths));
523
        }
524
        $pathCollections = array_map([$this, 'ensureAbsolutePaths'], $pathCollections);
525
        $pathCollections[] = $format;
526
        return $pathCollections;
527
    }
528
529
    /**
530
     * @param string $packageName
531
     * @return string
532
     */
533
    protected function getPackagePath(string $packageName): string
0 ignored issues
show
Unused Code introduced by
The parameter $packageName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
534
    {
535
        return '';
536
    }
537
538
    /**
539
     * Returns a unique identifier for the resolved layout file.
540
     * This identifier is based on the template path and last modification date
541
     *
542
     * @param string $layoutName The name of the layout
543
     * @return string layout identifier
544
     */
545
    public function getLayoutIdentifier(string $layoutName = 'Default'): string
546
    {
547
        $filePathAndFilename = $this->getLayoutPathAndFilename($layoutName);
548
        $layoutName = str_replace('.', '_', $layoutName);
549
        $prefix = 'layout_' . $layoutName . '_' . $this->getFormat();
550
        return $this->createIdentifierForFile($filePathAndFilename, $prefix);
551
    }
552
553
    /**
554
     * Resolve the path and file name of the layout file, based on
555
     * $this->layoutPathAndFilename and $this->layoutPathAndFilenamePattern.
556
     *
557
     * In case a layout has already been set with setLayoutPathAndFilename(),
558
     * this method returns that path, otherwise a path and filename will be
559
     * resolved using the layoutPathAndFilenamePattern.
560
     *
561
     * @param string $layoutName Name of the layout to use. If none given, use "Default"
562
     * @return string Path and filename of layout file
563
     * @throws InvalidTemplateResourceException
564
     */
565
    public function getLayoutSource(string $layoutName = 'Default'): string
566
    {
567
        $layoutPathAndFilename = $this->getLayoutPathAndFilename($layoutName);
568
        return file_get_contents($layoutPathAndFilename);
569
    }
570
571
    /**
572
     * Returns a unique identifier for the resolved template file
573
     * This identifier is based on the template path and last modification date
574
     *
575
     * @param string $controller
576
     * @param string $action Name of the action. If NULL, will be taken from request.
577
     * @return string template identifier
578
     */
579
    public function getTemplateIdentifier(string $controller = 'Default', string $action = 'Default'): string
580
    {
581
        if ($this->templateSource !== null) {
582
            return 'source_' . sha1($this->templateSource) . '_' . $controller . '_' . $action . '_' . $this->getFormat();
583
        }
584
        $templatePathAndFilename = $this->resolveTemplateFileForControllerAndActionAndFormat($controller, $action);
585
        $prefix = $controller . '_action_' . $action;
586
        return $this->createIdentifierForFile($templatePathAndFilename, $prefix);
587
    }
588
589
    /**
590
     * @param mixed $source
591
     */
592
    public function setTemplateSource($source)
593
    {
594
        $this->templateSource = $source;
595
    }
596
597
    /**
598
     * Resolve the template path and filename for the given action. If $actionName
599
     * is NULL, looks into the current request.
600
     *
601
     * @param string $controller
602
     * @param string $action Name of the action. If NULL, will be taken from request.
603
     * @return string Full path to template
604
     * @throws InvalidTemplateResourceException
605
     */
606
    public function getTemplateSource(string $controller = 'Default', string $action = 'Default'): string
607
    {
608
        if (is_string($this->templateSource)) {
609
            return $this->templateSource;
610
        } elseif (is_resource($this->templateSource)) {
611
            rewind($this->templateSource);
612
            return $this->templateSource = stream_get_contents($this->templateSource);
613
        }
614
        $templateReference = $this->resolveTemplateFileForControllerAndActionAndFormat($controller, $action);
615
        if (!$templateReference || ($templateReference !== 'php://stdin' && !file_exists($templateReference))) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $templateReference of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
616
            $format = $this->getFormat();
617
            throw new InvalidTemplateResourceException(
0 ignored issues
show
Deprecated Code introduced by
The class TYPO3Fluid\Fluid\View\Ex...mplateResourceException has been deprecated with message: Will be removed in Fluid 4.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
618
                sprintf(
619
                    'Tried resolving a template file for controller action "%s->%s" in format ".%s", but none of the paths ' .
620
                    'contained the expected template file (%s). %s',
621
                    $controller,
622
                    $action,
623
                    $format,
624
                    $templateReference === null ? $controller . '/' . ucfirst($action) . '.' . $format : $templateReference,
625
                    count($this->getTemplateRootPaths()) > 0 ? 'The following paths were checked: ' . implode(', ', $this->getTemplateRootPaths()) : 'No paths configured.'
626
                ),
627
                1257246929
628
            );
629
        }
630
        return file_get_contents($templateReference);
631
    }
632
633
    /**
634
     * Returns a unique identifier for the given file in the format
635
     * <PackageKey>_<SubPackageKey>_<ControllerName>_<prefix>_<SHA1>
636
     * The SH1 hash is a checksum that is based on the file path and last modification date
637
     *
638
     * @param string $pathAndFilename
639
     * @param string $prefix
640
     * @return string
641
     */
642
    protected function createIdentifierForFile(string $pathAndFilename, string $prefix): string
643
    {
644
        $templateModifiedTimestamp = $pathAndFilename !== 'php://stdin' ? filemtime($pathAndFilename) : 0;
645
        return sprintf('%s_%s', $prefix, sha1($pathAndFilename . '|' . $templateModifiedTimestamp));
646
    }
647
648
    /**
649
     * Resolve the path and file name of the layout file, based on
650
     * $this->options['layoutPathAndFilename'] and $this->options['layoutPathAndFilenamePattern'].
651
     *
652
     * In case a layout has already been set with setLayoutPathAndFilename(),
653
     * this method returns that path, otherwise a path and filename will be
654
     * resolved using the layoutPathAndFilenamePattern.
655
     *
656
     * @param string $layoutName Name of the layout to use. If none given, use "Default"
657
     * @return string Path and filename of layout files
658
     * @throws InvalidTemplateResourceException
659
     */
660
    public function getLayoutPathAndFilename(string $layoutName = 'Default'): string
661
    {
662
        if ($this->layoutPathAndFilename !== null) {
663
            return $this->layoutPathAndFilename;
664
        }
665
        $layoutName = ucfirst($layoutName);
666
        $layoutKey = $layoutName . '.' . $this->getFormat();
667
        if (!array_key_exists($layoutKey, $this->resolvedFiles[self::NAME_LAYOUTS])) {
668
            $paths = $this->getLayoutRootPaths();
669
            $this->resolvedFiles[self::NAME_LAYOUTS][$layoutKey] = $this->resolveFileInPaths($paths, $layoutName);
670
        }
671
        return $this->resolvedFiles[self::NAME_LAYOUTS][$layoutKey];
672
    }
673
674
    /**
675
     * Returns a unique identifier for the resolved partial file.
676
     * This identifier is based on the template path and last modification date
677
     *
678
     * @param string $partialName The name of the partial
679
     * @return string partial identifier
680
     */
681
    public function getPartialIdentifier(string $partialName)
682
    {
683
        $partialKey = $partialName . '.' . $this->getFormat();
684
        if (!array_key_exists($partialKey, $this->resolvedIdentifiers[self::NAME_PARTIALS])) {
685
            $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
686
            $prefix = 'partial_' . $partialName;
687
            $prefix = str_replace('/', '__', $prefix);
688
            $this->resolvedIdentifiers[self::NAME_PARTIALS][$partialKey] = $this->createIdentifierForFile($partialPathAndFilename, $prefix);
689
        }
690
        return $this->resolvedIdentifiers[self::NAME_PARTIALS][$partialKey];
691
    }
692
693
    /**
694
     * Figures out which partial to use.
695
     *
696
     * @param string $partialName The name of the partial
697
     * @return string contents of the partial template
698
     * @throws InvalidTemplateResourceException
699
     */
700
    public function getPartialSource(string $partialName)
701
    {
702
        $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
703
        return file_get_contents($partialPathAndFilename);
704
    }
705
706
    /**
707
     * Resolve the partial path and filename based on $this->options['partialPathAndFilenamePattern'].
708
     *
709
     * @param string $partialName The name of the partial
710
     * @return string the full path which should be used. The path definitely exists.
711
     * @throws InvalidTemplateResourceException
712
     */
713
    public function getPartialPathAndFilename(string $partialName)
714
    {
715
        $partialKey = $partialName . '.' . $this->getFormat();
716
        if (!array_key_exists($partialKey, $this->resolvedFiles[self::NAME_PARTIALS])) {
717
            $paths = $this->getPartialRootPaths();
718
            $partialName = ucfirst($partialName);
719
            $this->resolvedFiles[self::NAME_PARTIALS][$partialKey] = $this->resolveFileInPaths($paths, $partialName);
720
        }
721
        return $this->resolvedFiles[self::NAME_PARTIALS][$partialKey];
722
    }
723
724
    /**
725
     * @param array $paths
726
     * @param string $relativePathAndFilename
727
     * @param string $format Optional format to resolve.
728
     * @return string
729
     * @throws InvalidTemplateResourceException
730
     */
731
    protected function resolveFileInPaths(array $paths, string $relativePathAndFilename, string $format = null): string
732
    {
733
        $format = $format ?: $this->getFormat();
734
        $tried = [];
735
        // Note about loop: iteration with while + array_pop causes paths to be checked in opposite
736
        // order, which is intentional. Paths are considered overlays, e.g. adding a path to the
737
        // array means you want that path checked first.
738
        while (null !== ($path = array_pop($paths))) {
739
            $pathAndFilenameWithoutFormat = $path . $relativePathAndFilename;
740
            $pathAndFilename = $pathAndFilenameWithoutFormat . '.' . $format;
741
            if (is_file($pathAndFilename)) {
742
                return $pathAndFilename;
743
            }
744
            $tried[] = $pathAndFilename;
745
            if (is_file($pathAndFilenameWithoutFormat)) {
746
                return $pathAndFilenameWithoutFormat;
747
            }
748
            $tried[] = $pathAndFilenameWithoutFormat;
749
        }
750
        throw new InvalidTemplateResourceException(
0 ignored issues
show
Deprecated Code introduced by
The class TYPO3Fluid\Fluid\View\Ex...mplateResourceException has been deprecated with message: Will be removed in Fluid 4.0

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
751
            'The Fluid template files "' . implode('", "', $tried) . '" could not be loaded.',
752
            1225709595
753
        );
754
    }
755
756
    /**
757
     * @param string|NULL $type
758
     * @return void
759
     */
760
    protected function clearResolvedIdentifiersAndTemplates(?string $type = null): void
761
    {
762
        if ($type !== null) {
763
            $this->resolvedIdentifiers[$type] = $this->resolvedFiles[$type] = [];
764
        }
765
    }
766
}
767