Completed
Pull Request — development (#2329)
by Joshua
09:15
created

Templates::requireTemplate()   F

Complexity

Conditions 24
Paths 248

Size

Total Lines 77
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 88.3001
Metric Value
dl 0
loc 77
ccs 28
cts 54
cp 0.5185
rs 3.9655
cc 24
eloc 43
nc 248
nop 3
crap 88.3001

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
class Templates
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
4
{
5
    protected static $instance = null;
6
7
    public $dirs        = array();
8
    protected $delayed  = array();
9
    protected $templates = array();
10
    protected $default_loaded = false;
11
12 7
    public static function getInstance()
13
    {
14 7
        if (self::$instance === null)
15 7
        {
16
            self::$instance = new Templates;
17
        }
18
19 7
        return self::$instance;
20
    }
21
22
    protected function __construct()
23
    {
24
        // We want to be able to figure out any errors...
25
        @ini_set('track_errors', '1');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
26
    }
27
28
    public function setDirectories(array $dirs)
29
    {
30
        $this->dirs = $dirs;
31
    }
32
33
    /**
34
     * Load a template - if the theme doesn't include it, use the default.
35
     *
36
     * What it does:
37
     * - loads a template file with the name template_name from the current, default, or base theme.
38
     * - detects a wrong default theme directory and tries to work around it.
39
     * - can be used to only load style sheets by using false as the template name
40
     *   loading of style sheets with this function is deprecated, use loadCSSFile instead
41
     * - if $this->dirs is empty, it delays the loading of the template
42
     *
43
     * @uses $this->requireTemplate() to actually load the file.
44
     * @param string|false $template_name
45
     * @param string[]|string $style_sheets any style sheets to load with the template
46
     * @param bool $fatal = true if fatal is true, dies with an error message if the template cannot be found
47
     *
48
     * @return boolean|null
49
     */
50 1
    public function load($template_name, $style_sheets = array(), $fatal = true)
51
    {
52
        // If we don't know yet the default theme directory, let's wait a bit.
53 1
        if (empty($this->dirs))
54 1
        {
55
            $this->delayed[] = array(
56
                $template_name,
57
                $style_sheets,
58
                $fatal
59
            );
60
61
            return;
62
        }
63
        // If instead we know the default theme directory and we have delayed something, it's time to process
64 1
        elseif (!empty($this->delayed))
65
        {
66
            foreach ($this->delayed as $val)
67
            {
68
                $this->requireTemplate($val[0], $val[1], $val[2]);
69
            }
70
71
            // Forget about them (load them only once)
72
            $this->delayed = array();
73
        }
74
75 1
        $this->requireTemplate($template_name, $style_sheets, $fatal);
76 1
    }
77
78
    /**
79
     * <b>Internal function! Do not use it, use loadTemplate instead</b>
80
     *
81
     * What it does:
82
     * - loads a template file with the name template_name from the current, default, or base theme.
83
     * - detects a wrong default theme directory and tries to work around it.
84
     * - can be used to only load style sheets by using false as the template name
85
     *   loading of style sheets with this function is deprecated, use loadCSSFile instead
86
     *
87
     * @uses $this->templateInclude() to include the file.
88
     * @param string|false $template_name
89
     * @param string[]|string $style_sheets any style sheets to load with the template
90
     * @param bool $fatal = true if fatal is true, dies with an error message if the template cannot be found
91
     *
92
     * @return boolean|null
93
     */
94 1
    protected function requireTemplate($template_name, $style_sheets, $fatal)
0 ignored issues
show
Coding Style introduced by
requireTemplate uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
95
    {
96 1
        global $context, $settings, $txt, $scripturl, $db_show_debug;
97
98 1
        if (!is_array($style_sheets))
99 1
            $style_sheets = array($style_sheets);
100
101 1
        if ($this->default_loaded === false)
102 1
        {
103 1
            loadCSSFile('index.css');
104 1
            $this->default_loaded = true;
105 1
        }
106
107
        // Any specific template style sheets to load?
108 1
        if (!empty($style_sheets))
109 1
        {
110
            trigger_error('Use of loadTemplate to add style sheets to the head is deprecated.', E_USER_DEPRECATED);
111
            $sheets = array();
112
            foreach ($style_sheets as $sheet)
113
            {
114
                $sheets[] = stripos('.css', $sheet) !== false ? $sheet : $sheet . '.css';
115
                if ($sheet == 'admin' && !empty($context['theme_variant']))
116
                    $sheets[] = $context['theme_variant'] . '/admin' . $context['theme_variant'] . '.css';
117
            }
118
            loadCSSFile($sheets);
119
        }
120
121
        // No template to load?
122 1
        if ($template_name === false)
123 1
            return true;
124
125 1
        $loaded = false;
126 1
        foreach ($this->dirs as $template_dir)
127
        {
128 1
            if (file_exists($template_dir . '/' . $template_name . '.template.php'))
129 1
            {
130 1
                $loaded = true;
131 1
                $this->templateInclude($template_dir . '/' . $template_name . '.template.php', true);
132 1
                break;
133
            }
134 1
        }
135
136
        if ($loaded)
137 1
        {
138 1
            if ($db_show_debug === true)
139 1
                Debug::get()->add('templates', $template_name . ' (' . basename($template_dir) . ')');
0 ignored issues
show
Bug introduced by
The variable $template_dir seems to be defined by a foreach iteration on line 126. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
140
141
            // If they have specified an initialization function for this template, go ahead and call it now.
142 1
            if (function_exists('template_' . $template_name . '_init'))
143 1
                call_user_func('template_' . $template_name . '_init');
144 1
        }
145
        // Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
146
        elseif (!file_exists($settings['default_theme_dir']) && file_exists(BOARDDIR . '/themes/default'))
147
        {
148
            $settings['default_theme_dir'] = BOARDDIR . '/themes/default';
149
            $this->addDirectory($settings['default_theme_dir']);
150
151
            if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
152
            {
153
                loadLanguage('Errors');
154
155
                if (!isset($context['security_controls_files']['title']))
156
                    $context['security_controls_files']['title'] = $txt['generic_warning'];
157
158
                $context['security_controls_files']['errors']['theme_dir'] = '<a href="' . $scripturl . '?action=admin;area=theme;sa=list;th=1;' . $context['session_var'] . '=' . $context['session_id'] . '">' . $txt['theme_dir_wrong'] . '</a>';
159
            }
160
161
            loadTemplate($template_name);
162
        }
163
        // Cause an error otherwise.
164
        elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
165
            Errors::instance()->fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
166
        elseif ($fatal)
167
            die(Errors::instance()->log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load themes/default/%s.template.php!', (string) $template_name), 'template'));
0 ignored issues
show
Coding Style Compatibility introduced by
The method requireTemplate() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
168
        else
169
            return false;
170 1
    }
171
172
    /**
173
     * Load a sub-template.
174
     *
175
     * What it does:
176
     * - loads the sub template specified by sub_template_name, which must be in an already-loaded template.
177
     * - if ?debug is in the query string, shows administrators a marker after every sub template
178
     * for debugging purposes.
179
     *
180
     * @todo get rid of reading $_REQUEST directly
181
     * @param string $sub_template_name
182
     * @param bool|string $fatal = false, $fatal = true is for templates that
183
     *                 shouldn't get a 'pretty' error screen 'ignore' to skip
184
     */
185
    public function loadSubTemplate($sub_template_name, $fatal = false)
186
    {
187
        global $txt, $db_show_debug;
188
189
        if ($db_show_debug === true)
190
            Debug::get()->add('sub_templates', $sub_template_name);
191
192
        // Figure out what the template function is named.
193
        $theme_function = 'template_' . $sub_template_name;
194
195
        if (function_exists($theme_function))
196
            $theme_function();
197
        elseif ($fatal === false)
198
            Errors::instance()->fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
199
        elseif ($fatal !== 'ignore')
200
            die(Errors::instance()->log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load the %s sub template!', (string) $sub_template_name), 'template'));
0 ignored issues
show
Coding Style Compatibility introduced by
The method loadSubTemplate() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
201
    }
202
203
    /**
204
     * Load the template/language file using eval or require? (with eval we can show an error message!)
205
     *
206
     * What it does:
207
     * - loads the template or language file specified by filename.
208
     * - uses eval unless disableTemplateEval is enabled.
209
     * - outputs a parse error if the file did not exist or contained errors.
210
     * - attempts to detect the error and line, and show detailed information.
211
     *
212
     * @param string $filename
213
     * @param bool $once = false, if true only includes the file once (like include_once)
214
     */
215 7
    public function templateInclude($filename, $once = false)
216
    {
217
        // I know this looks weird but this is used to include $txt files. If the parent doesn't declare them global
218
        // the scope will be local to this function. IOW, don't remove this line!
219 7
        global $txt;
220
221
        // Don't include the file more than once, if $once is true.
222 7
        if ($once && in_array($filename, $this->templates))
223 7
        {
224
            return;
225
        }
226
        // Add this file to the include list, whether $once is true or not.
227
        else
228
        {
229 7
            $this->templates[] = $filename;
230
        }
231
232
        // Load it if we find it
233 7
        $file_found = file_exists($filename);
234
235 7
        if ($once && $file_found)
236 7
            require_once($filename);
237 6
        elseif ($file_found)
238 6
            require($filename);
239
240 7
        if ($file_found !== true)
241 7
            $this->templateNotFound($filename);
242 7
    }
243
244
    protected function templateNotFound($filename)
0 ignored issues
show
Coding Style introduced by
templateNotFound uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
245
    {
246
        global $context, $txt, $scripturl, $modSettings, $boardurl;
247
        global $maintenance, $mtitle, $mmessage;
248
249
        @ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
250
        if (!empty($modSettings['enableCompressedOutput']))
251
            ob_start('ob_gzhandler');
252
        else
253
            ob_start();
254
255
        if (isset($_GET['debug']))
256
            header('Content-Type: application/xhtml+xml; charset=UTF-8');
257
258
        // Don't cache error pages!!
259
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
260
        header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
261
        header('Cache-Control: no-cache');
262
263
        if (!isset($txt['template_parse_error']))
264
        {
265
            $txt['template_parse_error'] = 'Template Parse Error!';
266
            $txt['template_parse_error_message'] = 'It seems something has gone sour on the forum with the template system.  This problem should only be temporary, so please come back later and try again.  If you continue to see this message, please contact the administrator.<br /><br />You can also try <a href="javascript:location.reload();">refreshing this page</a>.';
267
            $txt['template_parse_error_details'] = 'There was a problem loading the <span style="font-family: monospace;"><strong>%1$s</strong></span> template or language file.  Please check the syntax and try again - remember, single quotes (<span style="font-family: monospace;">\'</span>) often have to be escaped with a slash (<span style="font-family: monospace;">\\</span>).  To see more specific error information from PHP, try <a href="%2$s%1$s" class="extern">accessing the file directly</a>.<br /><br />You may want to try to <a href="javascript:location.reload();">refresh this page</a> or <a href="%3$s">use the default theme</a>.';
268
            $txt['template_parse_undefined'] = 'An undefined error occurred during the parsing of this template';
269
        }
270
271
        // First, let's get the doctype and language information out of the way.
272
        echo '<!DOCTYPE html>
273
<html ', !empty($context['right_to_left']) ? 'dir="rtl"' : '', '>
274
	<head>
275
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
276
277
        if (!empty($maintenance) && !allowedTo('admin_forum')) {
278
            echo '
279
		<title>', $mtitle, '</title>
280
	</head>
281
	<body>
282
		<h3>', $mtitle, '</h3>
283
		', $mmessage, '
284
	</body>
285
</html>';
286
        }
287
        elseif (!allowedTo('admin_forum'))
288
        {
289
            echo '
290
		<title>', $txt['template_parse_error'], '</title>
291
	</head>
292
	<body>
293
		<h3>', $txt['template_parse_error'], '</h3>
294
		', $txt['template_parse_error_message'], '
295
	</body>
296
</html>';
297
        }
298
        else
299
        {
300
            require_once(SUBSDIR . '/Package.subs.php');
301
302
            $error = fetch_web_data($boardurl . strtr($filename, array(BOARDDIR => '', strtr(BOARDDIR, '\\', '/') => '')));
303
            if (empty($error) && ini_get('track_errors') && !empty($php_errormsg))
304
                $error = $php_errormsg;
305
            elseif (empty($error))
306
                $error = $txt['template_parse_undefined'];
307
308
            $error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
309
310
            echo '
311
		<title>', $txt['template_parse_error'], '</title>
312
	</head>
313
	<body>
314
		<h3>', $txt['template_parse_error'], '</h3>
315
		', sprintf($txt['template_parse_error_details'], strtr($filename, array(BOARDDIR => '', strtr(BOARDDIR, '\\', '/') => '')), $boardurl, $scripturl . '?theme=1');
316
317
            if (!empty($error))
318
                echo '
319
		<hr />
320
321
		<div style="margin: 0 20px;"><span style="font-family: monospace;">', strtr(strtr($error, array('<strong>' . BOARDDIR => '<strong>...', '<strong>' . strtr(BOARDDIR, '\\', '/') => '<strong>...')), '\\', '/'), '</span></div>';
322
323
            // I know, I know... this is VERY COMPLICATED.  Still, it's good.
324
            if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
325
            {
326
                $data = file($filename);
327
                $data2 = highlight_php_code(implode('', $data));
328
                $data2 = preg_split('~\<br( /)?\>~', $data2);
329
330
                // Fix the PHP code stuff...
331
                if (!isBrowser('gecko'))
332
                    $data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
333
                else
334
                    $data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
335
336
                // Now we get to work around a bug in PHP where it doesn't escape <br />s!
337
                $j = -1;
338
                foreach ($data as $line)
339
                {
340
                    $j++;
341
342
                    if (substr_count($line, '<br />') == 0)
343
                        continue;
344
345
                    $n = substr_count($line, '<br />');
346
                    for ($i = 0; $i < $n; $i++)
347
                    {
348
                        $data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
349
                        unset($data2[$j + $i + 1]);
350
                    }
351
                    $j += $n;
352
                }
353
                $data2 = array_values($data2);
354
                array_unshift($data2, '');
355
356
                echo '
357
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
358
359
                // Figure out what the color coding was before...
360
                $line = max($match[1] - 9, 1);
361
                $last_line = '';
362
                for ($line2 = $line - 1; $line2 > 1; $line2--)
363
                    if (strpos($data2[$line2], '<') !== false)
364
                    {
365
                        if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
366
                            $last_line = $color_match[1];
367
                        break;
368
                    }
369
370
                // Show the relevant lines...
371
                for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
372
                {
373
                    if ($line == $match[1])
374
                        echo '</pre><div style="background: #ffb0b5;"><pre style="margin: 0;">';
375
376
                    echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
377
                    if (isset($data2[$line]) && $data2[$line] != '')
378
                        echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
379
380
                    if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
381
                    {
382
                        $last_line = $color_match[1];
383
                        echo '</', substr($last_line, 1, 4), '>';
384
                    }
385
                    elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
386
                        $last_line = '';
387
                    elseif ($last_line != '' && $data2[$line] != '')
388
                        echo '</', substr($last_line, 1, 4), '>';
389
390
                    if ($line == $match[1])
391
                        echo '</pre></div><pre style="margin: 0;">';
392
                    else
393
                        echo "\n";
394
                }
395
396
                echo '</pre></div>';
397
            }
398
399
            echo '
400
	</body>
401
</html>';
402
        }
403
404
        die;
0 ignored issues
show
Coding Style Compatibility introduced by
The method templateNotFound() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
405
    }
406
407 1
    public function addDirectory($dir)
408
    {
409 1
        $this->dirs[] = (string) $dir;
410 1
        return $this;
411
    }
412
413 1
    public function reloadDirectories(array $settings)
414
    {
415 1
        $this->dirs = array();
416
417 1
        if (!empty($settings['theme_dir']))
418 1
        {
419 1
            $this->addDirectory($settings['theme_dir']);
420 1
        }
421
422
        // Based on theme (if there is one).
423 1
        if (!empty($settings['base_theme_dir']))
424 1
        {
425
            $this->addDirectory($settings['base_theme_dir']);
426
        }
427
428
        // Lastly the default theme.
429 1
        if ($settings['theme_dir'] !== $settings['default_theme_dir'])
430 1
        {
431
            $this->addDirectory($settings['default_theme_dir']);
432
        }
433 1
    }
434
435
    public function hasDirectories()
436
    {
437
        return !empty($this->dirs);
438
    }
439
440
    public function getTemplateDirectories()
441
    {
442
        return $this->dirs;
443
    }
444
445
    public function getIncludedTemplates()
446
    {
447
        return $this->templates;
448
    }
449
}