Completed
Push — master ( c17796...052b15 )
by Damian
01:29
created

SSViewer   F

Complexity

Total Complexity 81

Size/Duplication

Total Lines 786
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 786
rs 1.263
c 0
b 0
f 0
wmc 81

35 Methods

Rating   Name   Duplication   Size   Complexity  
C process() 0 80 10
A includeRequirements() 0 3 1
A setRewriteHashLinks() 0 4 1
A chooseTemplate() 0 3 1
A getTemplateFileByType() 0 3 1
A __construct() 0 20 4
B getSubtemplateFor() 0 26 4
A templates() 0 3 1
A getParser() 0 6 2
A flush_cacheblock_cache() 0 8 3
A parseTemplateContent() 0 6 2
B includeGeneratedTemplate() 0 21 5
A add_themes() 0 6 1
A set_theme() 0 4 1
A topLevel() 0 6 2
A getPartialCacheStore() 0 7 2
A hasTemplate() 0 3 1
B flush_template_cache() 0 10 5
A get_base_tag() 0 9 2
A exists() 0 3 1
A getRewriteHashLinksDefault() 0 7 2
A dontRewriteHashlinks() 0 3 1
A setParser() 0 3 1
B get_themes() 0 23 5
A flush() 0 4 1
A setPartialCacheStore() 0 3 1
C get_templates_by_class() 0 29 8
A fromString() 0 7 2
A set_themes() 0 3 1
A setRewriteHashLinksDefault() 0 3 1
A getRewriteHashLinks() 0 6 2
A setTemplate() 0 5 1
A setTemplateFile() 0 6 3
A execute_template() 0 6 1
A execute_string() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like SSViewer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SSViewer, and based on these observations, apply Extract Interface, too.

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 = [];
0 ignored issues
show
introduced by
The private property $themes is not used, and could be removed.
Loading history...
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;
0 ignored issues
show
introduced by
The private property $theme is not used, and could be removed.
Loading history...
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;
0 ignored issues
show
introduced by
The private property $theme_enabled is not used, and could be removed.
Loading history...
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';
0 ignored issues
show
introduced by
The private property $global_key is not used, and could be removed.
Loading history...
97
98
    /**
99
     * @config
100
     * @var bool
101
     */
102
    private static $source_file_comments = false;
0 ignored issues
show
introduced by
The private property $source_file_comments is not used, and could be removed.
Loading history...
103
104
    /**
105
     * Set if hash links should be rewritten
106
     *
107
     * @config
108
     * @var bool
109
     */
110
    private static $rewrite_hash_links = true;
0 ignored issues
show
introduced by
The private property $rewrite_hash_links is not used, and could be removed.
Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $themes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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);
0 ignored issues
show
Bug introduced by
$content of type string is incompatible with the type array expected by parameter $args of SilverStripe\View\SSViewer::create(). ( Ignorable by Annotation )

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

232
        $viewer = SSViewer_FromString::create(/** @scrutinizer ignore-type */ $content);
Loading history...
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();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
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')) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
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');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
280
        }
281
        if ($themes) {
282
            return $themes;
283
        }
284
285
        // Support legacy behaviour
286
        if ($theme = SSViewer::config()->uninherited('theme')) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $baseClass of type null|string is loosely compared to true; 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...
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) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
Bug Best Practice introduced by
The expression SilverStripe\View\SSViewer::topLevel of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
353
            return SSViewer::$topLevel[sizeof(SSViewer::$topLevel)-1];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
Bug introduced by
The call to sizeof() has too few arguments starting with mode. ( Ignorable by Annotation )

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

353
            return SSViewer::$topLevel[/** @scrutinizer ignore-call */ sizeof(SSViewer::$topLevel)-1];

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...
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like $templates can also be of type string. However, the property $templates is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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
Bug introduced by
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);
0 ignored issues
show
Bug introduced by
It seems like $inheritedScope can also be of type SilverStripe\View\ViewableData; however, parameter $inheritedScope of SilverStripe\View\SSView...resenter::__construct() does only seem to accept null|SilverStripe\View\SSViewer_Scope, maybe add an additional type check? ( Ignorable by Annotation )

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

592
        $scope = new SSViewer_DataPresenter($item, $overlay, $underlay, /** @scrutinizer ignore-type */ $inheritedScope);
Loading history...
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;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $fh can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

638
            fwrite(/** @scrutinizer ignore-type */ $fh, $content);
Loading history...
639
            fclose($fh);
0 ignored issues
show
Bug introduced by
It seems like $fh can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

639
            fclose(/** @scrutinizer ignore-type */ $fh);
Loading history...
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);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
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);
0 ignored issues
show
Bug introduced by
Are you sure $thisURLRelativeToBase of type string|array can be used in concatenation? ( Ignorable by Annotation )

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

688
                $output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . /** @scrutinizer ignore-type */ $thisURLRelativeToBase . '#', $output);
Loading history...
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);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
Bug introduced by
$template of type string is incompatible with the type array expected by parameter $args of SilverStripe\View\SSViewer::create(). ( Ignorable by Annotation )

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

747
        $v = SSViewer::create(/** @scrutinizer ignore-type */ $template);
Loading history...
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);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
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')
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
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