Completed
Pull Request — master (#348)
by Claus
02:12
created

TemplatePaths::resolveFilesInFolder()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 6
nop 2
dl 0
loc 19
rs 8.2222
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
        if (!is_dir($folder)) {
329
            return [];
330
        }
331
        $files = scandir($folder, SCANDIR_SORT_ASCENDING);
332
        $detected = [];
333
        foreach ($files ?: [] as $file) {
334
            $filePathAndFilename = rtrim($folder, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file;
335
            if ($file{0} === '.') {
336
                continue;
337
            } elseif (is_dir($filePathAndFilename)) {
338
                $detected = array_merge($detected, $this->resolveFilesInFolder($filePathAndFilename, $format));
339
            } elseif (pathinfo($filePathAndFilename, PATHINFO_EXTENSION) === $format) {
340
                $detected[] = $filePathAndFilename;
341
            }
342
        }
343
        return $detected;
344
    }
345
346
    /**
347
     * Fills path arrays based on a traditional
348
     * TypoScript array which may contain one or
349
     * more of the supported structures, in order
350
     * of priority:
351
     *
352
     * - `plugin.tx_yourext.view.templateRootPath` and siblings.
353
     * - `plugin.tx_yourext.view.templateRootPaths` and siblings.
354
     * - `plugin.tx_yourext.view.overlays.otherextension.templateRootPath` and siblings.
355
     *
356
     * The paths are treated as follows, using the
357
     * `template`-type paths as an example:
358
     *
359
     * - If `templateRootPath` is defined, it gets
360
     *   used as the _first_ path in the internal
361
     *   paths array.
362
     * - If `templateRootPaths` is defined, all
363
     *   values from it are _appended_ to the
364
     *   internal paths array.
365
     * - If `overlays.*` exists in the array it is
366
     *   iterated, each `templateRootPath` entry
367
     *   from it _appended_ to the internal array.
368
     *
369
     * The result is that after filling, the path
370
     * arrays will contain one or more entries in
371
     * the order described above, depending on how
372
     * many of the possible configurations were
373
     * present in the input array.
374
     *
375
     * Will replace any currently configured paths.
376
     *
377
     * @param array $paths
378
     * @return void
379
     * @api
380
     */
381
    public function fillFromConfigurationArray(array $paths)
382
    {
383
        list ($templateRootPaths, $layoutRootPaths, $partialRootPaths, $format) = $this->extractPathArrays($paths);
384
        $this->setTemplateRootPaths($templateRootPaths);
385
        $this->setLayoutRootPaths($layoutRootPaths);
386
        $this->setPartialRootPaths($partialRootPaths);
387
        $this->setFormat($format);
388
    }
389
390
    /**
391
     * Fills path arrays with default expected paths
392
     * based on package name (converted to extension
393
     * key automatically).
394
     *
395
     * Will replace any currently configured paths.
396
     *
397
     * @param string $packageName
398
     * @return void
399
     * @api
400
     */
401
    public function fillDefaultsByPackageName($packageName)
402
    {
403
        $path = $this->getPackagePath($packageName);
404
        $this->setTemplateRootPaths([$path . self::DEFAULT_TEMPLATES_DIRECTORY]);
405
        $this->setLayoutRootPaths([$path . self::DEFAULT_LAYOUTS_DIRECTORY]);
406
        $this->setPartialRootPaths([$path . self::DEFAULT_PARTIALS_DIRECTORY]);
407
    }
408
409
    /**
410
     * Sanitize a path, ensuring it is absolute and
411
     * if a directory, suffixed by a trailing slash.
412
     *
413
     * @param string|array $path
414
     * @return string
415
     */
416
    protected function sanitizePath($path)
417
    {
418
        if (is_array($path)) {
419
            $paths = array_map([$this, 'sanitizePath'], $path);
420
            return array_unique($paths);
421
        } elseif (strpos($path, 'php://') === 0) {
422
            return $path;
423
        } elseif (!empty($path)) {
424
            $path = str_replace(['\\', '//'], '/', (string) $path);
425
            $path = (string) $this->ensureAbsolutePath($path);
426
            if (is_dir($path)) {
427
                $path = $this->ensureSuffixedPath($path);
428
            }
429
        }
430
        return $path;
431
    }
432
433
    /**
434
     * Sanitize paths passing each through sanitizePath().
435
     *
436
     * @param array $paths
437
     * @return array
438
     */
439
    protected function sanitizePaths(array $paths)
440
    {
441
        return array_unique(array_map([$this, 'sanitizePath'], $paths));
442
    }
443
444
    /**
445
     * Guarantees that $reference is turned into a
446
     * correct, absolute path.
447
     *
448
     * @param string $path
449
     * @return string
450
     */
451
    protected function ensureAbsolutePath($path)
452
    {
453
        return ((!empty($path) && $path{0} !== '/' && $path{1} !== ':') ? $this->sanitizePath(realpath($path)) : $path);
454
    }
455
456
    /**
457
     * Guarantees that array $reference with paths
458
     * are turned into correct, absolute paths
459
     *
460
     * @param array $reference
461
     * @return array
462
     */
463
    protected function ensureAbsolutePaths(array $reference)
464
    {
465
        return array_map([$this, 'ensureAbsolutePath'], $reference);
466
    }
467
468
    /**
469
     * @param string $path
470
     * @return string
471
     */
472
    protected function ensureSuffixedPath($path)
473
    {
474
        return rtrim($path, '/') . '/';
475
    }
476
477
    /**
478
     * Extract an array of three arrays of paths, one
479
     * for each of the types of Fluid file resources.
480
     * Accepts one or both of the singular and plural
481
     * path definitions in the input - returns the
482
     * combined collections of paths based on both
483
     * the singular and plural entries with the singular
484
     * entries being recorded first and plurals second.
485
     *
486
     * Adds legacy singular name as last option, if set.
487
     *
488
     * @param array $paths
489
     * @return array
490
     */
491
    protected function extractPathArrays(array $paths)
492
    {
493
        $format = $this->getFormat();
494
        // pre-processing: if special parameters exist, extract them:
495
        if (isset($paths[self::CONFIG_FORMAT])) {
496
            $format = $paths[self::CONFIG_FORMAT];
497
        }
498
        $pathParts = [
499
            self::CONFIG_TEMPLATEROOTPATHS,
500
            self::CONFIG_LAYOUTROOTPATHS,
501
            self::CONFIG_PARTIALROOTPATHS
502
        ];
503
        $pathCollections = [];
504
        foreach ($pathParts as $pathPart) {
505
            $partPaths = [];
506
            if (isset($paths[$pathPart]) && is_array($paths[$pathPart])) {
507
                $partPaths = array_merge($partPaths, $paths[$pathPart]);
508
            }
509
            $pathCollections[] = array_unique(array_map([$this, 'ensureSuffixedPath'], $partPaths));
510
        }
511
        $pathCollections = array_map([$this, 'ensureAbsolutePaths'], $pathCollections);
512
        $pathCollections[] = $format;
513
        return $pathCollections;
514
    }
515
516
    /**
517
     * @param string $packageName
518
     * @return string
519
     */
520
    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...
521
    {
522
        return '';
523
    }
524
525
    /**
526
     * Returns a unique identifier for the resolved layout file.
527
     * This identifier is based on the template path and last modification date
528
     *
529
     * @param string $layoutName The name of the layout
530
     * @return string layout identifier
531
     */
532
    public function getLayoutIdentifier($layoutName = 'Default')
533
    {
534
        $filePathAndFilename = $this->getLayoutPathAndFilename($layoutName);
535
        $layoutName = str_replace('.', '_', $layoutName);
536
        $prefix = 'layout_' . $layoutName . '_' . $this->getFormat();
537
        return $this->createIdentifierForFile($filePathAndFilename, $prefix);
538
    }
539
540
    /**
541
     * Resolve the path and file name of the layout file, based on
542
     * $this->layoutPathAndFilename and $this->layoutPathAndFilenamePattern.
543
     *
544
     * In case a layout has already been set with setLayoutPathAndFilename(),
545
     * this method returns that path, otherwise a path and filename will be
546
     * resolved using the layoutPathAndFilenamePattern.
547
     *
548
     * @param string $layoutName Name of the layout to use. If none given, use "Default"
549
     * @return string Path and filename of layout file
550
     * @throws InvalidTemplateResourceException
551
     */
552
    public function getLayoutSource($layoutName = 'Default')
553
    {
554
        $layoutPathAndFilename = $this->getLayoutPathAndFilename($layoutName);
555
        return file_get_contents($layoutPathAndFilename, FILE_TEXT);
556
    }
557
558
    /**
559
     * Returns a unique identifier for the resolved template file
560
     * This identifier is based on the template path and last modification date
561
     *
562
     * @param string $controller
563
     * @param string $action Name of the action. If NULL, will be taken from request.
564
     * @return string template identifier
565
     */
566
    public function getTemplateIdentifier($controller = 'Default', $action = 'Default')
567
    {
568
        if ($this->templateSource !== null) {
569
            return 'source_' . sha1($this->templateSource) . '_' . $controller . '_' . $action . '_' . $this->getFormat();
570
        }
571
        $templatePathAndFilename = $this->resolveTemplateFileForControllerAndActionAndFormat($controller, $action);
572
        $prefix = $controller . '_action_' . $action;
573
        return $this->createIdentifierForFile($templatePathAndFilename, $prefix);
574
    }
575
576
    /**
577
     * @param mixed $source
578
     */
579
    public function setTemplateSource($source)
580
    {
581
        $this->templateSource = $source;
582
    }
583
584
    /**
585
     * Resolve the template path and filename for the given action. If $actionName
586
     * is NULL, looks into the current request.
587
     *
588
     * @param string $controller
589
     * @param string $action Name of the action. If NULL, will be taken from request.
590
     * @return string Full path to template
591
     * @throws InvalidTemplateResourceException
592
     */
593
    public function getTemplateSource($controller = 'Default', $action = 'Default')
594
    {
595
        if (is_string($this->templateSource)) {
596
            return $this->templateSource;
597
        } elseif (is_resource($this->templateSource)) {
598
            rewind($this->templateSource);
599
            return $this->templateSource = stream_get_contents($this->templateSource);
600
        }
601
        $templateReference = $this->resolveTemplateFileForControllerAndActionAndFormat($controller, $action);
602
        if (!file_exists($templateReference) && $templateReference !== 'php://stdin') {
603
            $format = $this->getFormat();
604
            throw new InvalidTemplateResourceException(
605
                sprintf(
606
                    'Tried resolving a template file for controller action "%s->%s" in format ".%s", but none of the paths ' .
607
                    'contained the expected template file (%s). %s',
608
                    $controller,
609
                    $action,
610
                    $format,
611
                    $templateReference === null ? $controller . '/' . ucfirst($action) . '.' . $format : $templateReference,
612
                    count($this->getTemplateRootPaths()) ? 'The following paths were checked: ' . implode(', ', $this->getTemplateRootPaths()) : 'No paths configured.'
613
                ),
614
                1257246929
615
            );
616
        }
617
        return file_get_contents($templateReference, FILE_TEXT);
618
    }
619
620
    /**
621
     * Returns a unique identifier for the given file in the format
622
     * <PackageKey>_<SubPackageKey>_<ControllerName>_<prefix>_<SHA1>
623
     * The SH1 hash is a checksum that is based on the file path and last modification date
624
     *
625
     * @param string $pathAndFilename
626
     * @param string $prefix
627
     * @return string
628
     */
629
    protected function createIdentifierForFile($pathAndFilename, $prefix)
630
    {
631
        $templateModifiedTimestamp = $pathAndFilename !== 'php://stdin' ? filemtime($pathAndFilename) : 0;
632
        return sprintf('%s_%s', $prefix, sha1($pathAndFilename . '|' . $templateModifiedTimestamp));
633
    }
634
635
    /**
636
     * Resolve the path and file name of the layout file, based on
637
     * $this->options['layoutPathAndFilename'] and $this->options['layoutPathAndFilenamePattern'].
638
     *
639
     * In case a layout has already been set with setLayoutPathAndFilename(),
640
     * this method returns that path, otherwise a path and filename will be
641
     * resolved using the layoutPathAndFilenamePattern.
642
     *
643
     * @param string $layoutName Name of the layout to use. If none given, use "Default"
644
     * @return string Path and filename of layout files
645
     * @throws Exception\InvalidTemplateResourceException
646
     */
647
    public function getLayoutPathAndFilename($layoutName = 'Default')
648
    {
649
        if ($this->layoutPathAndFilename !== null) {
650
            return $this->layoutPathAndFilename;
651
        }
652
        $layoutName = ucfirst($layoutName);
653
        $layoutKey = $layoutName . '.' . $this->getFormat();
654
        if (!array_key_exists($layoutKey, self::$resolvedFiles[self::NAME_LAYOUTS])) {
655
            $paths = $this->getLayoutRootPaths();
656
            self::$resolvedFiles[self::NAME_LAYOUTS][$layoutKey] = $this->resolveFileInPaths($paths, $layoutName);
657
        }
658
        return self::$resolvedFiles[self::NAME_LAYOUTS][$layoutKey];
659
    }
660
661
    /**
662
     * Returns a unique identifier for the resolved partial file.
663
     * This identifier is based on the template path and last modification date
664
     *
665
     * @param string $partialName The name of the partial
666
     * @return string partial identifier
667
     */
668
    public function getPartialIdentifier($partialName)
669
    {
670
        $partialKey = $partialName . '.' . $this->getFormat();
671
        if (!array_key_exists($partialKey, self::$resolvedIdentifiers[self::NAME_PARTIALS])) {
672
            $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
673
            $prefix = 'partial_' . $partialName;
674
            self::$resolvedIdentifiers[self::NAME_PARTIALS][$partialKey] = $this->createIdentifierForFile($partialPathAndFilename, $prefix);
675
        }
676
        return self::$resolvedIdentifiers[self::NAME_PARTIALS][$partialKey];
677
    }
678
679
    /**
680
     * Figures out which partial to use.
681
     *
682
     * @param string $partialName The name of the partial
683
     * @return string contents of the partial template
684
     * @throws InvalidTemplateResourceException
685
     */
686
    public function getPartialSource($partialName)
687
    {
688
        $partialPathAndFilename = $this->getPartialPathAndFilename($partialName);
689
        return file_get_contents($partialPathAndFilename, FILE_TEXT);
690
    }
691
692
    /**
693
     * Resolve the partial path and filename based on $this->options['partialPathAndFilenamePattern'].
694
     *
695
     * @param string $partialName The name of the partial
696
     * @return string the full path which should be used. The path definitely exists.
697
     * @throws InvalidTemplateResourceException
698
     */
699
    public function getPartialPathAndFilename($partialName)
700
    {
701
        $partialKey = $partialName . '.' . $this->getFormat();
702
        if (!array_key_exists($partialKey, self::$resolvedFiles[self::NAME_PARTIALS])) {
703
            $paths = $this->getPartialRootPaths();
704
            $partialName = ucfirst($partialName);
705
            self::$resolvedFiles[self::NAME_PARTIALS][$partialKey] = $this->resolveFileInPaths($paths, $partialName);
706
        }
707
        return self::$resolvedFiles[self::NAME_PARTIALS][$partialKey];
708
    }
709
710
    /**
711
     * @param array $paths
712
     * @param string $relativePathAndFilename
713
     * @param string $format Optional format to resolve.
714
     * @return string
715
     * @throws \TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException
716
     */
717
    protected function resolveFileInPaths(array $paths, $relativePathAndFilename, $format = null)
718
    {
719
        $format = $format ?: $this->getFormat();
720
        $tried = [];
721
        // Note about loop: iteration with while + array_pop causes paths to be checked in opposite
722
        // order, which is intentional. Paths are considered overlays, e.g. adding a path to the
723
        // array means you want that path checked first.
724
        while (null !== ($path = array_pop($paths))) {
725
            $pathAndFilenameWithoutFormat = $path . $relativePathAndFilename;
726
            $pathAndFilename = $pathAndFilenameWithoutFormat . '.' . $format;
727
            if (is_file($pathAndFilename)) {
728
                return $pathAndFilename;
729
            }
730
            $tried[] = $pathAndFilename;
731
            if (is_file($pathAndFilenameWithoutFormat)) {
732
                return $pathAndFilenameWithoutFormat;
733
            }
734
            $tried[] = $pathAndFilenameWithoutFormat;
735
        }
736
        throw new InvalidTemplateResourceException(
737
            'The Fluid template files "' . implode('", "', $tried) . '" could not be loaded.',
738
            1225709595
739
        );
740
    }
741
742
    /**
743
     * @param string|NULL $type
744
     * @return void
745
     */
746
    protected function clearResolvedIdentifiersAndTemplates($type = null)
747
    {
748
        if ($type !== null) {
749
            self::$resolvedIdentifiers[$type] = self::$resolvedFiles[$type] = [];
750
        } else {
751
            self::$resolvedIdentifiers = self::$resolvedFiles = [
752
                self::NAME_TEMPLATES => [],
753
                self::NAME_LAYOUTS => [],
754
                self::NAME_PARTIALS => []
755
            ];
756
        }
757
    }
758
}
759