Test Setup Failed
Push — master ( 210134...c17796 )
by Damian
03:18
created

src/View/SSViewer.php (1 issue)

1
<?php
2
3
namespace SilverStripe\View;
4
5
use SilverStripe\Core\Config\Config;
6
use SilverStripe\Core\Config\Configurable;
7
use SilverStripe\Core\ClassInfo;
8
use Psr\SimpleCache\CacheInterface;
9
use SilverStripe\Core\Convert;
10
use SilverStripe\Core\Flushable;
11
use SilverStripe\Core\Injector\Injector;
12
use SilverStripe\Core\Injector\Injectable;
13
use SilverStripe\Control\Director;
14
use SilverStripe\Dev\Deprecation;
15
use SilverStripe\ORM\FieldType\DBField;
16
use SilverStripe\ORM\FieldType\DBHTMLText;
17
use SilverStripe\Security\Permission;
18
use InvalidArgumentException;
19
20
/**
21
 * Parses a template file with an *.ss file extension.
22
 *
23
 * In addition to a full template in the templates/ folder, a template in
24
 * templates/Content or templates/Layout will be rendered into $Content and
25
 * $Layout, respectively.
26
 *
27
 * A single template can be parsed by multiple nested {@link SSViewer} instances
28
 * through $Layout/$Content placeholders, as well as <% include MyTemplateFile %> template commands.
29
 *
30
 * <b>Themes</b>
31
 *
32
 * See http://doc.silverstripe.org/themes and http://doc.silverstripe.org/themes:developing
33
 *
34
 * <b>Caching</b>
35
 *
36
 * Compiled templates are cached via {@link Cache}, usually on the filesystem.
37
 * If you put ?flush=1 on your URL, it will force the template to be recompiled.
38
 *
39
 * @see http://doc.silverstripe.org/themes
40
 * @see http://doc.silverstripe.org/themes:developing
41
 */
42
class SSViewer implements Flushable
43
{
44
    use Configurable;
45
    use Injectable;
46
47
    /**
48
     * Identifier for the default theme
49
     */
50
    const DEFAULT_THEME = '$default';
51
52
    /**
53
     * A list (highest priority first) of themes to use
54
     * Only used when {@link $theme_enabled} is set to TRUE.
55
     *
56
     * @config
57
     * @var string
58
     */
59
    private static $themes = [];
60
61
    /**
62
     * Overridden value of $themes config
63
     *
64
     * @var array
65
     */
66
    protected static $current_themes = null;
67
68
    /**
69
     * The used "theme", which usually consists of templates, images and stylesheets.
70
     * Only used when {@link $theme_enabled} is set to TRUE, and $themes is empty
71
     *
72
     * @deprecated 4.0..5.0
73
     * @config
74
     * @var string
75
     */
76
    private static $theme = null;
77
78
    /**
79
     * Use the theme. Set to FALSE in order to disable themes,
80
     * which can be useful for scenarios where theme overrides are temporarily undesired,
81
     * such as an administrative interface separate from the website theme.
82
     * It retains the theme settings to be re-enabled, for example when a website content
83
     * needs to be rendered from within this administrative interface.
84
     *
85
     * @config
86
     * @var bool
87
     */
88
    private static $theme_enabled = true;
89
90
    /**
91
     * Default prepended cache key for partial caching
92
     *
93
     * @config
94
     * @var string
95
     */
96
    private static $global_key = '$CurrentReadingMode, $CurrentUser.ID';
97
98
    /**
99
     * @config
100
     * @var bool
101
     */
102
    private static $source_file_comments = false;
103
104
    /**
105
     * Set if hash links should be rewritten
106
     *
107
     * @config
108
     * @var bool
109
     */
110
    private static $rewrite_hash_links = true;
111
112
    /**
113
     * Overridden value of rewrite_hash_links config
114
     *
115
     * @var bool
116
     */
117
    protected static $current_rewrite_hash_links = null;
118
119
    /**
120
     * Instance variable to disable rewrite_hash_links (overrides global default)
121
     * Leave null to use global state.
122
     *
123
     * @var bool|null
124
     */
125
    protected $rewriteHashlinks = null;
126
127
    /**
128
     * @internal
129
     * @ignore
130
     */
131
    private static $template_cache_flushed = false;
132
133
    /**
134
     * @internal
135
     * @ignore
136
     */
137
    private static $cacheblock_cache_flushed = false;
138
139
    /**
140
     * List of items being processed
141
     *
142
     * @var array
143
     */
144
    protected static $topLevel = [];
145
146
    /**
147
     * List of templates to select from
148
     *
149
     * @var array
150
     */
151
    protected $templates = null;
152
153
    /**
154
     * Absolute path to chosen template file
155
     *
156
     * @var string
157
     */
158
    protected $chosen = null;
159
160
    /**
161
     * Templates to use when looking up 'Layout' or 'Content'
162
     *
163
     * @var array
164
     */
165
    protected $subTemplates = null;
166
167
    /**
168
     * @var bool
169
     */
170
    protected $includeRequirements = true;
171
172
    /**
173
     * @var TemplateParser
174
     */
175
    protected $parser;
176
177
    /**
178
     * @var CacheInterface
179
     */
180
    protected $partialCacheStore = null;
181
182
    /**
183
     * @param string|array $templates If passed as a string with .ss extension, used as the "main" template.
184
     *  If passed as an array, it can be used for template inheritance (first found template "wins").
185
     *  Usually the array values are PHP class names, which directly correlate to template names.
186
     *  <code>
187
     *  array('MySpecificPage', 'MyPage', 'Page')
188
     *  </code>
189
     * @param TemplateParser $parser
190
     */
191
    public function __construct($templates, TemplateParser $parser = null)
192
    {
193
        if ($parser) {
194
            $this->setParser($parser);
195
        }
196
197
        $this->setTemplate($templates);
198
199
        if (!$this->chosen) {
200
            $message = 'None of the following templates could be found: ';
201
            $message .= print_r($templates, true);
202
203
            $themes = self::get_themes();
204
            if (!$themes) {
205
                $message .= ' (no theme in use)';
206
            } else {
207
                $message .= ' in themes "' . print_r($themes, true) . '"';
208
            }
209
210
            user_error($message, E_USER_WARNING);
211
        }
212
    }
213
214
    /**
215
     * Triggered early in the request when someone requests a flush.
216
     */
217
    public static function flush()
218
    {
219
        self::flush_template_cache(true);
220
        self::flush_cacheblock_cache(true);
221
    }
222
223
    /**
224
     * Create a template from a string instead of a .ss file
225
     *
226
     * @param string $content The template content
227
     * @param bool|void $cacheTemplate Whether or not to cache the template from string
228
     * @return SSViewer
229
     */
230
    public static function fromString($content, $cacheTemplate = null)
231
    {
232
        $viewer = SSViewer_FromString::create($content);
233
        if ($cacheTemplate !== null) {
234
            $viewer->setCacheTemplate($cacheTemplate);
235
        }
236
        return $viewer;
237
    }
238
239
    /**
240
     * Assign the list of active themes to apply.
241
     * If default themes should be included add $default as the last entry.
242
     *
243
     * @param array $themes
244
     */
245
    public static function set_themes($themes = [])
246
    {
247
        static::$current_themes = $themes;
248
    }
249
250
    /**
251
     * Add to the list of active themes to apply
252
     *
253
     * @param array $themes
254
     */
255
    public static function add_themes($themes = [])
256
    {
257
        $currentThemes = SSViewer::get_themes();
258
        $finalThemes = array_merge($themes, $currentThemes);
259
        // array_values is used to ensure sequential array keys as array_unique can leave gaps
260
        static::set_themes(array_values(array_unique($finalThemes)));
261
    }
262
263
    /**
264
     * Get the list of active themes
265
     *
266
     * @return array
267
     */
268
    public static function get_themes()
269
    {
270
        $default = [self::DEFAULT_THEME];
271
272
        if (!SSViewer::config()->uninherited('theme_enabled')) {
273
            return $default;
274
        }
275
276
        // Explicit list is assigned
277
        $themes = static::$current_themes;
278
        if (!isset($themes)) {
279
            $themes = SSViewer::config()->uninherited('themes');
280
        }
281
        if ($themes) {
282
            return $themes;
283
        }
284
285
        // Support legacy behaviour
286
        if ($theme = SSViewer::config()->uninherited('theme')) {
287
            return [$theme, self::DEFAULT_THEME];
288
        }
289
290
        return $default;
291
    }
292
293
    /**
294
     * @deprecated 4.0.0:5.0.0 Use the "SSViewer#set_themes" instead
295
     * @param string $theme The "base theme" name (without underscores).
296
     */
297
    public static function set_theme($theme)
298
    {
299
        Deprecation::notice('4.0', 'Use the "SSViewer#set_themes" instead');
300
        self::set_themes([$theme, self::DEFAULT_THEME]);
301
    }
302
303
    /**
304
     * Traverses the given the given class context looking for candidate template names
305
     * which match each item in the class hierarchy. The resulting list of template candidates
306
     * may or may not exist, but you can invoke {@see SSViewer::chooseTemplate} on any list
307
     * to determine the best candidate based on the current themes.
308
     *
309
     * @param string|object $classOrObject Valid class name, or object
310
     * @param string $suffix
311
     * @param string $baseClass Class to halt ancestry search at
312
     * @return array
313
     */
314
    public static function get_templates_by_class($classOrObject, $suffix = '', $baseClass = null)
315
    {
316
        // Figure out the class name from the supplied context.
317
        if (!is_object($classOrObject) && !(
318
            is_string($classOrObject) && class_exists($classOrObject)
319
        )) {
320
            throw new InvalidArgumentException(
321
                'SSViewer::get_templates_by_class() expects a valid class name as its first parameter.'
322
            );
323
        }
324
325
        $templates = [];
326
        $classes = array_reverse(ClassInfo::ancestry($classOrObject));
327
        foreach ($classes as $class) {
328
            $template = $class . $suffix;
329
            $templates[] = $template;
330
            $templates[] = ['type' => 'Includes', $template];
331
332
            // If the class is "PageController" (PSR-2 compatibility) or "Page_Controller" (legacy), look for Page.ss
333
            if (preg_match('/^(?<name>.+[^\\\\])_?Controller$/iU', $class, $matches)) {
334
                $templates[] = $matches['name'] . $suffix;
335
            }
336
337
            if ($baseClass && $class == $baseClass) {
338
                break;
339
            }
340
        }
341
342
        return $templates;
343
    }
344
345
    /**
346
     * Get the current item being processed
347
     *
348
     * @return ViewableData
349
     */
350
    public static function topLevel()
351
    {
352
        if (SSViewer::$topLevel) {
353
            return SSViewer::$topLevel[sizeof(SSViewer::$topLevel)-1];
354
        }
355
        return null;
356
    }
357
358
    /**
359
     * Check if rewrite hash links are enabled on this instance
360
     *
361
     * @return bool
362
     */
363
    public function getRewriteHashLinks()
364
    {
365
        if (isset($this->rewriteHashlinks)) {
366
            return $this->rewriteHashlinks;
367
        }
368
        return static::getRewriteHashLinksDefault();
369
    }
370
371
    /**
372
     * Set if hash links are rewritten for this instance
373
     *
374
     * @param bool $rewrite
375
     * @return $this
376
     */
377
    public function setRewriteHashLinks($rewrite)
378
    {
379
        $this->rewriteHashlinks = $rewrite;
380
        return $this;
381
    }
382
383
    /**
384
     * Get default value for rewrite hash links for all modules
385
     *
386
     * @return bool
387
     */
388
    public static function getRewriteHashLinksDefault()
389
    {
390
        // Check if config overridden
391
        if (isset(static::$current_rewrite_hash_links)) {
392
            return static::$current_rewrite_hash_links;
393
        }
394
        return Config::inst()->get(static::class, 'rewrite_hash_links');
395
    }
396
397
    /**
398
     * Set default rewrite hash links
399
     *
400
     * @param bool $rewrite
401
     */
402
    public static function setRewriteHashLinksDefault($rewrite)
403
    {
404
        static::$current_rewrite_hash_links = $rewrite;
405
    }
406
407
    /**
408
     * @param string|array $templates
409
     */
410
    public function setTemplate($templates)
411
    {
412
        $this->templates = $templates;
413
        $this->chosen = $this->chooseTemplate($templates);
414
        $this->subTemplates = [];
415
    }
416
417
    /**
418
     * Find the template to use for a given list
419
     *
420
     * @param array|string $templates
421
     * @return string
422
     */
423
    public static function chooseTemplate($templates)
424
    {
425
        return ThemeResourceLoader::inst()->findTemplate($templates, self::get_themes());
426
    }
427
428
    /**
429
     * Set the template parser that will be used in template generation
430
     *
431
     * @param TemplateParser $parser
432
     */
433
    public function setParser(TemplateParser $parser)
434
    {
435
        $this->parser = $parser;
436
    }
437
438
    /**
439
     * Returns the parser that is set for template generation
440
     *
441
     * @return TemplateParser
442
     */
443
    public function getParser()
444
    {
445
        if (!$this->parser) {
446
            $this->setParser(Injector::inst()->get('SilverStripe\\View\\SSTemplateParser'));
447
        }
448
        return $this->parser;
449
    }
450
451
    /**
452
     * Returns true if at least one of the listed templates exists.
453
     *
454
     * @param array|string $templates
455
     *
456
     * @return bool
457
     */
458
    public static function hasTemplate($templates)
459
    {
460
        return (bool)ThemeResourceLoader::inst()->findTemplate($templates, self::get_themes());
461
    }
462
463
    /**
464
     * Call this to disable rewriting of <a href="#xxx"> links.  This is useful in Ajax applications.
465
     * It returns the SSViewer objects, so that you can call new SSViewer("X")->dontRewriteHashlinks()->process();
466
     *
467
     * @return $this
468
     */
469
    public function dontRewriteHashlinks()
470
    {
471
        return $this->setRewriteHashLinks(false);
472
    }
473
474
    /**
475
     * @return string
476
     */
477
    public function exists()
478
    {
479
        return $this->chosen;
480
    }
481
482
    /**
483
     * @param string $identifier A template name without '.ss' extension or path
484
     * @param string $type The template type, either "main", "Includes" or "Layout"
485
     * @return string Full system path to a template file
486
     */
487
    public static function getTemplateFileByType($identifier, $type = null)
488
    {
489
        return ThemeResourceLoader::inst()->findTemplate(['type' => $type, $identifier], self::get_themes());
490
    }
491
492
    /**
493
     * Clears all parsed template files in the cache folder.
494
     *
495
     * Can only be called once per request (there may be multiple SSViewer instances).
496
     *
497
     * @param bool $force Set this to true to force a re-flush. If left to false, flushing
498
     * may only be performed once a request.
499
     */
500
    public static function flush_template_cache($force = false)
501
    {
502
        if (!self::$template_cache_flushed || $force) {
503
            $dir = dir(TEMP_PATH);
0 ignored issues
show
The call to dir() has too few arguments starting with context. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

503
            $dir = /** @scrutinizer ignore-call */ dir(TEMP_PATH);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
504
            while (false !== ($file = $dir->read())) {
505
                if (strstr($file, '.cache')) {
506
                    unlink(TEMP_PATH . DIRECTORY_SEPARATOR . $file);
507
                }
508
            }
509
            self::$template_cache_flushed = true;
510
        }
511
    }
512
513
    /**
514
     * Clears all partial cache blocks.
515
     *
516
     * Can only be called once per request (there may be multiple SSViewer instances).
517
     *
518
     * @param bool $force Set this to true to force a re-flush. If left to false, flushing
519
     * may only be performed once a request.
520
     */
521
    public static function flush_cacheblock_cache($force = false)
522
    {
523
        if (!self::$cacheblock_cache_flushed || $force) {
524
            $cache = Injector::inst()->get(CacheInterface::class . '.cacheblock');
525
            $cache->clear();
526
527
528
            self::$cacheblock_cache_flushed = true;
529
        }
530
    }
531
532
    /**
533
     * Set the cache object to use when storing / retrieving partial cache blocks.
534
     *
535
     * @param CacheInterface $cache
536
     */
537
    public function setPartialCacheStore($cache)
538
    {
539
        $this->partialCacheStore = $cache;
540
    }
541
542
    /**
543
     * Get the cache object to use when storing / retrieving partial cache blocks.
544
     *
545
     * @return CacheInterface
546
     */
547
    public function getPartialCacheStore()
548
    {
549
        if ($this->partialCacheStore) {
550
            return $this->partialCacheStore;
551
        }
552
553
        return Injector::inst()->get(CacheInterface::class . '.cacheblock');
554
    }
555
556
    /**
557
     * Flag whether to include the requirements in this response.
558
     *
559
     * @param bool
560
     */
561
    public function includeRequirements($incl = true)
562
    {
563
        $this->includeRequirements = $incl;
564
    }
565
566
    /**
567
     * An internal utility function to set up variables in preparation for including a compiled
568
     * template, then do the include
569
     *
570
     * Effectively this is the common code that both SSViewer#process and SSViewer_FromString#process call
571
     *
572
     * @param string $cacheFile The path to the file that contains the template compiled to PHP
573
     * @param ViewableData $item The item to use as the root scope for the template
574
     * @param array $overlay Any variables to layer on top of the scope
575
     * @param array $underlay Any variables to layer underneath the scope
576
     * @param ViewableData $inheritedScope The current scope of a parent template including a sub-template
577
     * @return string The result of executing the template
578
     */
579
    protected function includeGeneratedTemplate($cacheFile, $item, $overlay, $underlay, $inheritedScope = null)
580
    {
581
        if (isset($_GET['showtemplate']) && $_GET['showtemplate'] && Permission::check('ADMIN')) {
582
            $lines = file($cacheFile);
583
            echo "<h2>Template: $cacheFile</h2>";
584
            echo "<pre>";
585
            foreach ($lines as $num => $line) {
586
                echo str_pad($num+1, 5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
587
            }
588
            echo "</pre>";
589
        }
590
591
        $cache = $this->getPartialCacheStore();
592
        $scope = new SSViewer_DataPresenter($item, $overlay, $underlay, $inheritedScope);
593
        $val = '';
594
595
        // Placeholder for values exposed to $cacheFile
596
        [$cache, $scope, $val];
597
        include($cacheFile);
598
599
        return $val;
600
    }
601
602
    /**
603
     * The process() method handles the "meat" of the template processing.
604
     *
605
     * It takes care of caching the output (via {@link Cache}), as well as
606
     * replacing the special "$Content" and "$Layout" placeholders with their
607
     * respective subtemplates.
608
     *
609
     * The method injects extra HTML in the header via {@link Requirements::includeInHTML()}.
610
     *
611
     * Note: You can call this method indirectly by {@link ViewableData->renderWith()}.
612
     *
613
     * @param ViewableData $item
614
     * @param array|null $arguments Arguments to an included template
615
     * @param ViewableData $inheritedScope The current scope of a parent template including a sub-template
616
     * @return DBHTMLText Parsed template output.
617
     */
618
    public function process($item, $arguments = null, $inheritedScope = null)
619
    {
620
        // Set hashlinks and temporarily modify global state
621
        $rewrite = $this->getRewriteHashLinks();
622
        $origRewriteDefault = static::getRewriteHashLinksDefault();
623
        static::setRewriteHashLinksDefault($rewrite);
624
625
        SSViewer::$topLevel[] = $item;
626
627
        $template = $this->chosen;
628
629
        $cacheFile = TEMP_PATH . DIRECTORY_SEPARATOR . '.cache'
630
            . str_replace(['\\','/',':'], '.', Director::makeRelative(realpath($template)));
631
        $lastEdited = filemtime($template);
632
633
        if (!file_exists($cacheFile) || filemtime($cacheFile) < $lastEdited) {
634
            $content = file_get_contents($template);
635
            $content = $this->parseTemplateContent($content, $template);
636
637
            $fh = fopen($cacheFile, 'w');
638
            fwrite($fh, $content);
639
            fclose($fh);
640
        }
641
642
        $underlay = ['I18NNamespace' => basename($template)];
643
644
        // Makes the rendered sub-templates available on the parent item,
645
        // through $Content and $Layout placeholders.
646
        foreach (['Content', 'Layout'] as $subtemplate) {
647
            // Detect sub-template to use
648
            $sub = $this->getSubtemplateFor($subtemplate);
649
            if (!$sub) {
650
                continue;
651
            }
652
653
            // Create lazy-evaluated underlay for this subtemplate
654
            $underlay[$subtemplate] = function () use ($item, $arguments, $sub) {
655
                $subtemplateViewer = clone $this;
656
                // Disable requirements - this will be handled by the parent template
657
                $subtemplateViewer->includeRequirements(false);
658
                // Select the right template
659
                $subtemplateViewer->setTemplate($sub);
660
661
                // Render if available
662
                if ($subtemplateViewer->exists()) {
663
                    return $subtemplateViewer->process($item, $arguments);
664
                }
665
                return null;
666
            };
667
        }
668
669
        $output = $this->includeGeneratedTemplate($cacheFile, $item, $arguments, $underlay, $inheritedScope);
670
671
        if ($this->includeRequirements) {
672
            $output = Requirements::includeInHTML($output);
673
        }
674
675
        array_pop(SSViewer::$topLevel);
676
677
        // If we have our crazy base tag, then fix # links referencing the current page.
678
        if ($rewrite) {
679
            if (strpos($output, '<base') !== false) {
680
                if ($rewrite === 'php') {
681
                    $thisURLRelativeToBase = <<<PHP
682
<?php echo \\SilverStripe\\Core\\Convert::raw2att(preg_replace("/^(\\\\/)+/", "/", \$_SERVER['REQUEST_URI'])); ?>
683
PHP;
684
                } else {
685
                    $thisURLRelativeToBase = Convert::raw2att(preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI']));
686
                }
687
688
                $output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output);
689
            }
690
        }
691
692
        /** @var DBHTMLText $html */
693
        $html = DBField::create_field('HTMLFragment', $output);
694
695
        // Reset global state
696
        static::setRewriteHashLinksDefault($origRewriteDefault);
697
        return $html;
698
    }
699
700
    /**
701
     * Get the appropriate template to use for the named sub-template, or null if none are appropriate
702
     *
703
     * @param string $subtemplate Sub-template to use
704
     *
705
     * @return array|null
706
     */
707
    protected function getSubtemplateFor($subtemplate)
708
    {
709
        // Get explicit subtemplate name
710
        if (isset($this->subTemplates[$subtemplate])) {
711
            return $this->subTemplates[$subtemplate];
712
        }
713
714
        // Don't apply sub-templates if type is already specified (e.g. 'Includes')
715
        if (isset($this->templates['type'])) {
716
            return null;
717
        }
718
719
        // Filter out any other typed templates as we can only add, not change type
720
        $templates = array_filter(
721
            (array)$this->templates,
722
            function ($template) {
723
                return !isset($template['type']);
724
            }
725
        );
726
        if (empty($templates)) {
727
            return null;
728
        }
729
730
        // Set type to subtemplate
731
        $templates['type'] = $subtemplate;
732
        return $templates;
733
    }
734
735
    /**
736
     * Execute the given template, passing it the given data.
737
     * Used by the <% include %> template tag to process templates.
738
     *
739
     * @param string $template Template name
740
     * @param mixed $data Data context
741
     * @param array $arguments Additional arguments
742
     * @param Object $scope
743
     * @return string Evaluated result
744
     */
745
    public static function execute_template($template, $data, $arguments = null, $scope = null)
746
    {
747
        $v = SSViewer::create($template);
748
        $v->includeRequirements(false);
749
750
        return $v->process($data, $arguments, $scope);
751
    }
752
753
    /**
754
     * Execute the evaluated string, passing it the given data.
755
     * Used by partial caching to evaluate custom cache keys expressed using
756
     * template expressions
757
     *
758
     * @param string $content Input string
759
     * @param mixed $data Data context
760
     * @param array $arguments Additional arguments
761
     * @return string Evaluated result
762
     */
763
    public static function execute_string($content, $data, $arguments = null)
764
    {
765
        $v = SSViewer::fromString($content);
766
        $v->includeRequirements(false);
767
768
        return $v->process($data, $arguments);
769
    }
770
771
    /**
772
     * Parse given template contents
773
     *
774
     * @param string $content The template contents
775
     * @param string $template The template file name
776
     * @return string
777
     */
778
    public function parseTemplateContent($content, $template = "")
779
    {
780
        return $this->getParser()->compileString(
781
            $content,
782
            $template,
783
            Director::isDev() && SSViewer::config()->uninherited('source_file_comments')
784
        );
785
    }
786
787
    /**
788
     * Returns the filenames of the template that will be rendered.  It is a map that may contain
789
     * 'Content' & 'Layout', and will have to contain 'main'
790
     *
791
     * @return array
792
     */
793
    public function templates()
794
    {
795
        return array_merge(['main' => $this->chosen], $this->subTemplates);
796
    }
797
798
    /**
799
     * @param string $type "Layout" or "main"
800
     * @param string $file Full system path to the template file
801
     */
802
    public function setTemplateFile($type, $file)
803
    {
804
        if (!$type || $type == 'main') {
805
            $this->chosen = $file;
806
        } else {
807
            $this->subTemplates[$type] = $file;
808
        }
809
    }
810
811
    /**
812
     * Return an appropriate base tag for the given template.
813
     * It will be closed on an XHTML document, and unclosed on an HTML document.
814
     *
815
     * @param string $contentGeneratedSoFar The content of the template generated so far; it should contain
816
     * the DOCTYPE declaration.
817
     * @return string
818
     */
819
    public static function get_base_tag($contentGeneratedSoFar)
820
    {
821
        $base = Director::absoluteBaseURL();
822
823
        // Is the document XHTML?
824
        if (preg_match('/<!DOCTYPE[^>]+xhtml/i', $contentGeneratedSoFar)) {
825
            return "<base href=\"$base\" />";
826
        } else {
827
            return "<base href=\"$base\"><!--[if lte IE 6]></base><![endif]-->";
828
        }
829
    }
830
}
831