Completed
Push — master ( 7acde5...b89d9d )
by Marc
02:16
created

TemplatePaths::getLayoutRootPaths()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
namespace TYPO3Fluid\Fluid\View;
3
4
/*
5
 * This file belongs to the package "TYPO3 Fluid".
6
 * See LICENSE.txt that was shipped with this package.
7
 */
8
use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException;
9
10
/**
11
 * Template Paths Holder
12
 *
13
 * Class used to hold and resolve template files
14
 * and paths in multiple supported ways.
15
 *
16
 * The purpose of this class is to homogenise the
17
 * API that is used when working with template
18
 * paths coming from TypoScript, as well as serve
19
 * as a way to quickly generate default template-,
20
 * layout- and partial root paths by package.
21
 *
22
 * The constructor accepts two different types of
23
 * input - anything not of those types is silently
24
 * ignored:
25
 *
26
 * - a `string` input is assumed a package name
27
 *   and will call the `fillDefaultsByPackageName`
28
 *   value filling method.
29
 * - an `array` input is assumed a TypoScript-style
30
 *   array of root paths in one or more of the
31
 *   supported structures and will call the
32
 *   `fillFromTypoScriptArray` method.
33
 *
34
 * Either method can also be called after instance
35
 * is created, but both will overwrite any paths
36
 * you have previously configured.
37
 */
38
class TemplatePaths
39
{
40
41
    const DEFAULT_FORMAT = 'html';
42
    const DEFAULT_TEMPLATES_DIRECTORY = 'Resources/Private/Templates/';
43
    const DEFAULT_LAYOUTS_DIRECTORY = 'Resources/Private/Layouts/';
44
    const DEFAULT_PARTIALS_DIRECTORY = 'Resources/Private/Partials/';
45
    const CONFIG_TEMPLATEROOTPATHS = 'templateRootPaths';
46
    const CONFIG_LAYOUTROOTPATHS = 'layoutRootPaths';
47
    const CONFIG_PARTIALROOTPATHS = 'partialRootPaths';
48
    const CONFIG_FORMAT = 'format';
49
    const NAME_TEMPLATES = 'templates';
50
    const NAME_LAYOUTS = 'layouts';
51
    const NAME_PARTIALS = 'partials';
52
53
    /**
54
     * Holds already resolved identifiers for template files
55
     *
56
     * @var array
57
     */
58
    protected static $resolvedIdentifiers = [
59
        self::NAME_TEMPLATES => [],
60
        self::NAME_LAYOUTS => [],
61
        self::NAME_PARTIALS => []
62
    ];
63
64
    /**
65
     * Holds already resolved identifiers for template files
66
     *
67
     * @var array
68
     */
69
    protected static $resolvedFiles = [
70
        self::NAME_TEMPLATES => [],
71
        self::NAME_LAYOUTS => [],
72
        self::NAME_PARTIALS => []
73
    ];
74
75
    /**
76
     * @var array
77
     */
78
    protected $templateRootPaths = [];
79
80
    /**
81
     * @var array
82
     */
83
    protected $layoutRootPaths = [];
84
85
    /**
86
     * @var array
87
     */
88
    protected $partialRootPaths = [];
89
90
    /**
91
     * @var string
92
     */
93
    protected $templatePathAndFilename = null;
94
95
    /**
96
     * @var string
97
     */
98
    protected $layoutPathAndFilename = null;
99
100
    /**
101
     * @var string|NULL
102
     */
103
    protected $templateSource = null;
104
105
    /**
106
     * @var string
107
     */
108
    protected $format = self::DEFAULT_FORMAT;
109
110
    /**
111
     * @param string|NULL $packageNameOrArray
112
     */
113
    public function __construct($packageNameOrArray = null)
114
    {
115
        $this->clearResolvedIdentifiersAndTemplates();
116
        if (is_array($packageNameOrArray)) {
117
            $this->fillFromConfigurationArray($packageNameOrArray);
118
        } elseif (!empty($packageNameOrArray)) {
119
            $this->fillDefaultsByPackageName($packageNameOrArray);
120
        }
121
    }
122
123
    /**
124
     * @return array
125
     */
126
    public function toArray()
127
    {
128
        return [
129
            self::CONFIG_TEMPLATEROOTPATHS => $this->sanitizePaths($this->getTemplateRootPaths()),
130
            self::CONFIG_LAYOUTROOTPATHS => $this->sanitizePaths($this->getLayoutRootPaths()),
131
            self::CONFIG_PARTIALROOTPATHS => $this->sanitizePaths($this->getPartialRootPaths())
132
        ];
133
    }
134
135
    /**
136
     * @param string $templatePathAndFilename
137
     * @return void
138
     */
139
    public function setTemplatePathAndFilename($templatePathAndFilename)
140
    {
141
        $this->templatePathAndFilename = (string) $this->sanitizePath($templatePathAndFilename);
142
    }
143
144
    /**
145
     * @param string $layoutPathAndFilename
146
     * @return void
147
     */
148
    public function setLayoutPathAndFilename($layoutPathAndFilename)
149
    {
150
        $this->layoutPathAndFilename = (string) $this->sanitizePath($layoutPathAndFilename);
151
    }
152
153
    /**
154
     * @return array
155
     */
156
    public function getTemplateRootPaths()
157
    {
158
        return $this->templateRootPaths;
159
    }
160
161
    /**
162
     * @param array $templateRootPaths
163
     * @return void
164
     */
165
    public function setTemplateRootPaths(array $templateRootPaths)
166
    {
167
        $this->templateRootPaths = (array) $this->sanitizePaths($templateRootPaths);
168
        $this->clearResolvedIdentifiersAndTemplates(self::NAME_TEMPLATES);
169
    }
170
171
    /**
172
     * @return array
173
     */
174
    public function getLayoutRootPaths()
175
    {
176
        return $this->layoutRootPaths;
177
    }
178
179
    /**
180
     * @param array $layoutRootPaths
181
     * @return void
182
     */
183
    public function setLayoutRootPaths(array $layoutRootPaths)
184
    {
185
        $this->layoutRootPaths = (array) $this->sanitizePaths($layoutRootPaths);
186
        $this->clearResolvedIdentifiersAndTemplates(self::NAME_LAYOUTS);
187
    }
188
189
    /**
190
     * @return array
191
     */
192
    public function getPartialRootPaths()
193
    {
194
        return $this->partialRootPaths;
195
    }
196
197
    /**
198
     * @param array $partialRootPaths
199
     * @return void
200
     */
201
    public function setPartialRootPaths(array $partialRootPaths)
202
    {
203
        $this->partialRootPaths = (array) $this->sanitizePaths($partialRootPaths);
204
        $this->clearResolvedIdentifiersAndTemplates(self::NAME_PARTIALS);
205
    }
206
207
    /**
208
     * @return string
209
     */
210
    public function getFormat()
211
    {
212
        return $this->format;
213
    }
214
215
    /**
216
     * @param string $format
217
     * @return void
218
     */
219
    public function setFormat($format)
220
    {
221
        $this->format = $format;
222
    }
223
224
    /**
225
     * Attempts to resolve an absolute filename
226
     * of a template (i.e. `templateRootPaths`)
227
     * using a controller name, action and format.
228
     *
229
     * Works _backwards_ through template paths in
230
     * order to achieve an "overlay"-type behavior
231
     * where the last paths added are the first to
232
     * be checked and the first path added acts as
233
     * fallback if no other paths have the file.
234
     *
235
     * If the file does not exist in any path,
236
     * including fallback path, `NULL` is returned.
237
     *
238
     * Path configurations filled from TypoScript
239
     * is automatically recorded in the right
240
     * order (see `fillFromTypoScriptArray`), but
241
     * when manually setting the paths that should
242
     * be checked, you as user must be aware of
243
     * this reverse behavior (which you should
244
     * already be, given that it is the same way
245
     * TypoScript path configurations work).
246
     *
247
     * @param string $controller
248
     * @param string $action
249
     * @param string $format
250
     * @return string|NULL
251
     * @api
252
     */
253
    public function resolveTemplateFileForControllerAndActionAndFormat($controller, $action, $format = null)
254
    {
255
        if ($this->templatePathAndFilename !== null) {
256
            return $this->templatePathAndFilename;
257
        }
258
        $format = $format ?: $this->getFormat();
259
        $controller = str_replace('\\', '/', $controller);
260
        $action = ucfirst($action);
261
        $identifier = $controller . '/' . $action . '.' . $format;
262
        if (!array_key_exists($identifier, self::$resolvedFiles['templates'])) {
263
            $templateRootPaths = $this->getTemplateRootPaths();
264
            foreach ([$controller . '/' . $action, $action] as $possibleRelativePath) {
265
                try {
266
                    return self::$resolvedFiles['templates'][$identifier] = $this->resolveFileInPaths($templateRootPaths, $possibleRelativePath, $format);
267
                } catch (InvalidTemplateResourceException $error) {
268
                    self::$resolvedFiles['templates'][$identifier] = null;
269
                }
270
            }
271
        }
272
        return isset(self::$resolvedFiles[self::NAME_TEMPLATES][$identifier]) ? self::$resolvedFiles[self::NAME_TEMPLATES][$identifier] : null;
273
    }
274
275
    /**
276
     * @param string|NULL $controllerName
277
     * @param string $format
278
     * @return array
279
     */
280
    public function resolveAvailableTemplateFiles($controllerName, $format = null)
281
    {
282
        $paths = $this->getTemplateRootPaths();
283
        foreach ($paths as $index => $path) {
284
            $paths[$index] = rtrim($path . $controllerName, '/') . '/';
285
        }
286
        return $this->resolveFilesInFolders($paths, $format ?: $this->getFormat());
287
    }
288
289
    /**
290
     * @param string $format
291
     * @return array
292
     */
293
    public function resolveAvailablePartialFiles($format = null)
294
    {
295
        return $this->resolveFilesInFolders($this->getPartialRootPaths(), $format ?: $this->getFormat());
296
    }
297
298
    /**
299
     * @param string $format
300
     * @return array
301
     */
302
    public function resolveAvailableLayoutFiles($format = null)
303
    {
304
        return $this->resolveFilesInFolders($this->getLayoutRootPaths(), $format ?: $this->getFormat());
305
    }
306
307
    /**
308
     * @param array $folders
309
     * @param string $format
310
     * @return array
311
     */
312
    protected function resolveFilesInFolders(array $folders, $format)
313
    {
314
        $files = [];
315
        foreach ($folders as $folder) {
316
            $files = array_merge($files, $this->resolveFilesInFolder($folder, $format));
317
        }
318
        return array_values($files);
319
    }
320
321
    /**
322
     * @param string $folder
323
     * @param string $format
324
     * @return array
325
     */
326
    protected function resolveFilesInFolder($folder, $format)
327
    {
328
        $files = glob($folder . '*.' . $format);
329
        return !$files ? [] : $files;
330
    }
331
332
    /**
333
     * Fills path arrays based on a traditional
334
     * TypoScript array which may contain one or
335
     * more of the supported structures, in order
336
     * of priority:
337
     *
338
     * - `plugin.tx_yourext.view.templateRootPath` and siblings.
339
     * - `plugin.tx_yourext.view.templateRootPaths` and siblings.
340
     * - `plugin.tx_yourext.view.overlays.otherextension.templateRootPath` and siblings.
341
     *
342
     * The paths are treated as follows, using the
343
     * `template`-type paths as an example:
344
     *
345
     * - If `templateRootPath` is defined, it gets
346
     *   used as the _first_ path in the internal
347
     *   paths array.
348
     * - If `templateRootPaths` is defined, all
349
     *   values from it are _appended_ to the
350
     *   internal paths array.
351
     * - If `overlays.*` exists in the array it is
352
     *   iterated, each `templateRootPath` entry
353
     *   from it _appended_ to the internal array.
354
     *
355
     * The result is that after filling, the path
356
     * arrays will contain one or more entries in
357
     * the order described above, depending on how
358
     * many of the possible configurations were
359
     * present in the input array.
360
     *
361
     * Will replace any currently configured paths.
362
     *
363
     * @param array $paths
364
     * @return void
365
     * @api
366
     */
367
    public function fillFromConfigurationArray(array $paths)
368
    {
369
        list ($templateRootPaths, $layoutRootPaths, $partialRootPaths, $format) = $this->extractPathArrays($paths);
370
        $this->setTemplateRootPaths($templateRootPaths);
371
        $this->setLayoutRootPaths($layoutRootPaths);
372
        $this->setPartialRootPaths($partialRootPaths);
373
        $this->setFormat($format);
374
    }
375
376
    /**
377
     * Fills path arrays with default expected paths
378
     * based on package name (converted to extension
379
     * key automatically).
380
     *
381
     * Will replace any currently configured paths.
382
     *
383
     * @param string $packageName
384
     * @return void
385
     * @api
386
     */
387
    public function fillDefaultsByPackageName($packageName)
388
    {
389
        $path = $this->getPackagePath($packageName);
390
        $this->setTemplateRootPaths([$path . self::DEFAULT_TEMPLATES_DIRECTORY]);
391
        $this->setLayoutRootPaths([$path . self::DEFAULT_LAYOUTS_DIRECTORY]);
392
        $this->setPartialRootPaths([$path . self::DEFAULT_PARTIALS_DIRECTORY]);
393
    }
394
395
    /**
396
     * Sanitize a path, ensuring it is absolute and
397
     * if a directory, suffixed by a trailing slash.
398
     *
399
     * @param string|array $path
400
     * @return string
401
     */
402
    protected function sanitizePath($path)
403
    {
404
        if (is_array($path)) {
405
            $paths = array_map([$this, 'sanitizePath'], $path);
406
            return array_unique($paths);
407
        } elseif (strpos($path, 'php://') === 0) {
408
            return $path;
409
        } elseif (!empty($path)) {
410
            $path = str_replace(['\\', '//'], '/', (string) $path);
411
            $path = (string) $this->ensureAbsolutePath($path);
412
            if (is_dir($path)) {
413
                $path = $this->ensureSuffixedPath($path);
414
            }
415
        }
416
        return $path;
417
    }
418
419
    /**
420
     * Sanitize paths passing each through sanitizePath().
421
     *
422
     * @param array $paths
423
     * @return array
424
     */
425
    protected function sanitizePaths(array $paths)
426
    {
427
        return array_unique(array_map([$this, 'sanitizePath'], $paths));
428
    }
429
430
    /**
431
     * Guarantees that $reference is turned into a
432
     * correct, absolute path.
433
     *
434
     * @param string $path
435
     * @return string
436
     */
437
    protected function ensureAbsolutePath($path)
438
    {
439
        return ((!empty($path) && $path{0} !== '/' && $path{1} !== ':') ? $this->sanitizePath(realpath($path)) : $path);
440
    }
441
442
    /**
443
     * Guarantees that array $reference with paths
444
     * are turned into correct, absolute paths
445
     *
446
     * @param array $reference
447
     * @return array
448
     */
449
    protected function ensureAbsolutePaths(array $reference)
450
    {
451
        return array_map([$this, 'ensureAbsolutePath'], $reference);
452
    }
453
454
    /**
455
     * @param string $path
456
     * @return string
457
     */
458
    protected function ensureSuffixedPath($path)
459
    {
460
        return rtrim($path, '/') . '/';
461
    }
462
463
    /**
464
     * Extract an array of three arrays of paths, one
465
     * for each of the types of Fluid file resources.
466
     * Accepts one or both of the singular and plural
467
     * path definitions in the input - returns the
468
     * combined collections of paths based on both
469
     * the singular and plural entries with the singular
470
     * entries being recorded first and plurals second.
471
     *
472
     * Adds legacy singular name as last option, if set.
473
     *
474
     * @param array $paths
475
     * @return array
476
     */
477
    protected function extractPathArrays(array $paths)
478
    {
479
        $format = $this->getFormat();
480
        // pre-processing: if special parameters exist, extract them:
481
        if (isset($paths[self::CONFIG_FORMAT])) {
482
            $format = $paths[self::CONFIG_FORMAT];
483
        }
484
        $pathParts = [
485
            self::CONFIG_TEMPLATEROOTPATHS,
486
            self::CONFIG_LAYOUTROOTPATHS,
487
            self::CONFIG_PARTIALROOTPATHS
488
        ];
489
        $pathCollections = [];
490
        foreach ($pathParts as $pathPart) {
491
            $partPaths = [];
492
            if (isset($paths[$pathPart]) && is_array($paths[$pathPart])) {
493
                $partPaths = array_merge($partPaths, $paths[$pathPart]);
494
            }
495
            $pathCollections[] = array_unique(array_map([$this, 'ensureSuffixedPath'], $partPaths));
496
        }
497
        $pathCollections = array_map([$this, 'ensureAbsolutePaths'], $pathCollections);
498
        $pathCollections[] = $format;
499
        return $pathCollections;
500
    }
501
502
    /**
503
     * @param string $packageName
504
     * @return string
505
     */
506
    protected function getPackagePath($packageName)
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...
507
    {
508
        return '';
509
    }
510
511
    /**
512
     * Returns a unique identifier for the resolved layout file.
513
     * This identifier is based on the template path and last modification date
514
     *
515
     * @param string $layoutName The name of the layout
516
     * @return string layout identifier
517
     */
518
    public function getLayoutIdentifier($layoutName = 'Default')
519
    {
520
        $filePathAndFilename = $this->getLayoutPathAndFilename($layoutName);
521
        $layoutName = str_replace('.', '_', $layoutName);
522
        $prefix = 'layout_' . $layoutName . '_' . $this->getFormat();
523
        return $this->createIdentifierForFile($filePathAndFilename, $prefix);
524
    }
525
526
    /**
527
     * Resolve the path and file name of the layout file, based on
528
     * $this->layoutPathAndFilename and $this->layoutPathAndFilenamePattern.
529
     *
530
     * In case a layout has already been set with setLayoutPathAndFilename(),
531
     * this method returns that path, otherwise a path and filename will be
532
     * resolved using the layoutPathAndFilenamePattern.
533
     *
534
     * @param string $layoutName Name of the layout to use. If none given, use "Default"
535
     * @return string Path and filename of layout file
536
     * @throws InvalidTemplateResourceException
537
     */
538
    public function getLayoutSource($layoutName = 'Default')
539
    {
540
        $layoutPathAndFilename = $this->getLayoutPathAndFilename($layoutName);
541
        return file_get_contents($layoutPathAndFilename, FILE_TEXT);
542
    }
543
544
    /**
545
     * Returns a unique identifier for the resolved template file
546
     * This identifier is based on the template path and last modification date
547
     *
548
     * @param string $controller
549
     * @param string $action Name of the action. If NULL, will be taken from request.
550
     * @return string template identifier
551
     */
552
    public function getTemplateIdentifier($controller = 'Default', $action = 'Default')
553
    {
554
        if ($this->templateSource !== null) {
555
            return 'source_' . sha1($this->templateSource) . '_' . $controller . '_' . $action . '_' . $this->getFormat();
556
        }
557
        $templatePathAndFilename = $this->resolveTemplateFileForControllerAndActionAndFormat($controller, $action);
558
        $prefix = $controller . '_action_' . $action;
559
        return $this->createIdentifierForFile($templatePathAndFilename, $prefix);
560
    }
561
562
    /**
563
     * @param mixed $source
564
     */
565
    public function setTemplateSource($source)
566
    {
567
        $this->templateSource = $source;
568
    }
569
570
    /**
571
     * Resolve the template path and filename for the given action. If $actionName
572
     * is NULL, looks into the current request.
573
     *
574
     * @param string $controller
575
     * @param string $action Name of the action. If NULL, will be taken from request.
576
     * @return string Full path to template
577
     * @throws InvalidTemplateResourceException
578
     */
579
    public function getTemplateSource($controller = 'Default', $action = 'Default')
580
    {
581
        if (is_string($this->templateSource)) {
582
            return $this->templateSource;
583
        } elseif (is_resource($this->templateSource)) {
584
            rewind($this->templateSource);
585
            return $this->templateSource = stream_get_contents($this->templateSource);
586
        }
587
        $templateReference = $this->resolveTemplateFileForControllerAndActionAndFormat($controller, $action);
588
        if (!file_exists($templateReference) && $templateReference !== 'php://stdin') {
589
            $format = $this->getFormat();
590
            throw new InvalidTemplateResourceException(
591
                sprintf(
592
                    'Tried resolving a template file for controller action "%s->%s" in format ".%s", but none of the paths ' .
593
                    'contained the expected template file (%s). %s',
594
                    $controller,
595
                    $action,
596
                    $format,
597
                    $templateReference === null ? $controller . '/' . ucfirst($action) . '.' . $format : $templateReference,
598
                    count($this->getTemplateRootPaths()) ? 'The following paths were checked: ' . implode(', ', $this->getTemplateRootPaths()) : 'No paths configured.'
599
                ),
600
                1257246929
601
            );
602
        }
603
        return file_get_contents($templateReference, FILE_TEXT);
604
    }
605
606
    /**
607
     * Returns a unique identifier for the given file in the format
608
     * <PackageKey>_<SubPackageKey>_<ControllerName>_<prefix>_<SHA1>
609
     * The SH1 hash is a checksum that is based on the file path and last modification date
610
     *
611
     * @param string $pathAndFilename
612
     * @param string $prefix
613
     * @return string
614
     */
615
    protected function createIdentifierForFile($pathAndFilename, $prefix)
616
    {
617
        $templateModifiedTimestamp = $pathAndFilename !== 'php://stdin' ? filemtime($pathAndFilename) : 0;
618
        return sprintf('%s_%s', $prefix, sha1($pathAndFilename . '|' . $templateModifiedTimestamp));
619
    }
620
621
    /**
622
     * Resolve the path and file name of the layout file, based on
623
     * $this->options['layoutPathAndFilename'] and $this->options['layoutPathAndFilenamePattern'].
624
     *
625
     * In case a layout has already been set with setLayoutPathAndFilename(),
626
     * this method returns that path, otherwise a path and filename will be
627
     * resolved using the layoutPathAndFilenamePattern.
628
     *
629
     * @param string $layoutName Name of the layout to use. If none given, use "Default"
630
     * @return string Path and filename of layout files
631
     * @throws Exception\InvalidTemplateResourceException
632
     */
633
    public function getLayoutPathAndFilename($layoutName = 'Default')
634
    {
635
        if ($this->layoutPathAndFilename !== null) {
636
            return $this->layoutPathAndFilename;
637
        }
638
        $layoutName = ucfirst($layoutName);
639
        $layoutKey = $layoutName . '.' . $this->getFormat();
640
        if (!array_key_exists($layoutKey, self::$resolvedFiles[self::NAME_LAYOUTS])) {
641
            $paths = $this->getLayoutRootPaths();
642
            self::$resolvedFiles[self::NAME_LAYOUTS][$layoutKey] = $this->resolveFileInPaths($paths, $layoutName);
643
        }
644
        return self::$resolvedFiles[self::NAME_LAYOUTS][$layoutKey];
645
    }
646
647
    /**
648
     * Returns a unique identifier for the resolved partial file.
649
     * This identifier is based on the template path and last modification date
650
     *
651
     * @param string $partialName The name of the partial
652
     * @return string partial identifier
653
     */
654
    public function getPartialIdentifier($partialName)
655
    {
656
        $partialKey = $partialName . '.' . $this->getFormat();
657
        if (!array_key_exists($partialKey, self::$resolvedIdentifiers[self::NAME_PARTIALS])) {
658
            $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
659
            $prefix = 'partial_' . $partialName;
660
            self::$resolvedIdentifiers[self::NAME_PARTIALS][$partialKey] = $this->createIdentifierForFile($partialPathAndFilename, $prefix);
661
        }
662
        return self::$resolvedIdentifiers[self::NAME_PARTIALS][$partialKey];
663
    }
664
665
    /**
666
     * Figures out which partial to use.
667
     *
668
     * @param string $partialName The name of the partial
669
     * @return string contents of the partial template
670
     * @throws InvalidTemplateResourceException
671
     */
672
    public function getPartialSource($partialName)
673
    {
674
        $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
675
        return file_get_contents($partialPathAndFilename, FILE_TEXT);
676
    }
677
678
    /**
679
     * Resolve the partial path and filename based on $this->options['partialPathAndFilenamePattern'].
680
     *
681
     * @param string $partialName The name of the partial
682
     * @return string the full path which should be used. The path definitely exists.
683
     * @throws InvalidTemplateResourceException
684
     */
685
    public function getPartialPathAndFilename($partialName)
686
    {
687
        $partialKey = $partialName . '.' . $this->getFormat();
688
        if (!array_key_exists($partialKey, self::$resolvedFiles[self::NAME_PARTIALS])) {
689
            $paths = $this->getPartialRootPaths();
690
            $partialName = ucfirst($partialName);
691
            self::$resolvedFiles[self::NAME_PARTIALS][$partialKey] = $this->resolveFileInPaths($paths, $partialName);
692
        }
693
        return self::$resolvedFiles[self::NAME_PARTIALS][$partialKey];
694
    }
695
696
    /**
697
     * @param array $paths
698
     * @param string $relativePathAndFilename
699
     * @param string $format Optional format to resolve.
700
     * @return string
701
     * @throws \TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException
702
     */
703
    protected function resolveFileInPaths(array $paths, $relativePathAndFilename, $format = null)
704
    {
705
        $format = $format ?: $this->getFormat();
706
        $tried = [];
707
        // Note about loop: iteration with while + array_pop causes paths to be checked in opposite
708
        // order, which is intentional. Paths are considered overlays, e.g. adding a path to the
709
        // array means you want that path checked first.
710
        while (null !== ($path = array_pop($paths))) {
711
            $pathAndFilenameWithoutFormat = $path . $relativePathAndFilename;
712
            $pathAndFilename = $pathAndFilenameWithoutFormat . '.' . $format;
713
            if (is_file($pathAndFilename)) {
714
                return $pathAndFilename;
715
            }
716
            $tried[] = $pathAndFilename;
717
            if (is_file($pathAndFilenameWithoutFormat)) {
718
                return $pathAndFilenameWithoutFormat;
719
            }
720
            $tried[] = $pathAndFilenameWithoutFormat;
721
        }
722
        throw new InvalidTemplateResourceException(
723
            'The Fluid template files "' . implode('", "', $tried) . '" could not be loaded.',
724
            1225709595
725
        );
726
    }
727
728
    /**
729
     * @param string|NULL $type
730
     * @return void
731
     */
732
    protected function clearResolvedIdentifiersAndTemplates($type = null)
733
    {
734
        if ($type !== null) {
735
            self::$resolvedIdentifiers[$type] = self::$resolvedFiles[$type] = [];
736
        } else {
737
            self::$resolvedIdentifiers = self::$resolvedFiles = [
738
                self::NAME_TEMPLATES => [],
739
                self::NAME_LAYOUTS => [],
740
                self::NAME_PARTIALS => []
741
            ];
742
        }
743
    }
744
}
745