Passed
Push — patch_1-1-7 ( 90a951...ab62ad )
by Emanuele
04:23 queued 03:46
created

Templates   F

Complexity

Total Complexity 93

Size/Duplication

Total Lines 607
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 195
dl 0
loc 607
rs 2
c 2
b 0
f 0
wmc 93

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getIncludedTemplates() 0 3 1
A __construct() 0 6 2
B loadSubTemplate() 0 32 7
A instance() 0 8 2
A load() 0 26 4
D templateNotFound() 0 184 30
B _templateDebug() 0 6 8
A setDirectories() 0 3 1
F requireTemplate() 0 98 24
A addDirectory() 0 5 1
A hasDirectories() 0 3 1
A reloadDirectories() 0 19 4
A getTemplateDirectories() 0 3 1
B templateInclude() 0 32 7

How to fix   Complexity   

Complex Class

Complex classes like Templates 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 Templates, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file has functions dealing with loading and precessing template files.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:    2011 Simple Machines (http://www.simplemachines.org)
12
 * license:        BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version 1.1.6
15
 *
16
 */
17
18
/**
19
 * Class Templates
20
 *
21
 * This class loads and processes template files and sheets.
22
 */
23
class Templates
24
{
25
	protected static $instance = null;
26
27
	/**
28
	 * Template directory's that we will be searching for the sheets
29
	 * @var array
30
	 */
31
	public $dirs = array();
32
33
	/**
34
	 * Template sheets that have not loaded
35
	 * @var array
36
	 */
37
	protected $delayed = array();
38
39
	/**
40
	 * Holds the file that are in the include list
41
	 * @var array
42
	 */
43
	protected $templates = array();
44
45
	/**
46
	 * Tracks if the default index.css has been loaded
47
	 * @var bool
48
	 */
49
	protected $default_loaded = false;
50
51
	/**
52
	 * Templates constructor.
53
	 */
54
	protected function __construct()
55
	{
56
		// We want to be able to figure out any errors...
57
		if (version_compare(PHP_VERSION, '7.0.0', '>='))
58
		{
59
			error_clear_last();
60
		}
61
	}
62
63
	/**
64
	 * Load a template - if the theme doesn't include it, use the default.
65
	 *
66
	 * What it does:
67
	 *
68
	 * - Loads a template file with the name template_name from the current, default, or base theme.
69
	 * - Detects a wrong default theme directory and tries to work around it.
70
	 * - Can be used to only load style sheets by using false as the template name
71
	 *   loading of style sheets with this function is deprecated, use loadCSSFile instead
72
	 * - If $this->dirs is empty, it delays the loading of the template
73
	 *
74
	 * @uses $this->requireTemplate() to actually load the file.
75
	 *
76
	 * @param string|false $template_name
77
	 * @param string[]|string $style_sheets any style sheets to load with the template
78
	 * @param bool $fatal = true if fatal is true, dies with an error message if the template cannot be found
79
	 *
80
	 * @return boolean|null
81
	 * @throws Elk_Exception
82
	 */
83
	public function load($template_name, $style_sheets = array(), $fatal = true)
84
	{
85
		// If we don't know yet the default theme directory, let's wait a bit.
86
		if (empty($this->dirs))
87
		{
88
			$this->delayed[] = array(
89
				$template_name,
90
				$style_sheets,
91
				$fatal
92
			);
93
94
			return;
95
		}
96
		// If instead we know the default theme directory and we have delayed something, it's time to process
97
		elseif (!empty($this->delayed))
98
		{
99
			foreach ($this->delayed as $val)
100
			{
101
				$this->requireTemplate($val[0], $val[1], $val[2]);
102
			}
103
104
			// Forget about them (load them only once)
105
			$this->delayed = array();
106
		}
107
108
		$this->requireTemplate($template_name, $style_sheets, $fatal);
109
	}
110
111
	/**
112
	 * <b>Internal function! Do not use it, use loadTemplate instead</b>
113
	 *
114
	 * What it does:
115
	 *
116
	 * - Loads a template file with the name template_name from the current, default, or base theme.
117
	 * - Detects a wrong default theme directory and tries to work around it.
118
	 * - Can be used to only load style sheets by using false as the template name
119
	 *  loading of style sheets with this function is deprecated, use loadCSSFile instead
120
	 *
121
	 * @uses $this->templateInclude() to include the file.
122
	 *
123
	 * @param string|false    $template_name
124
	 * @param string[]|string $style_sheets any style sheets to load with the template
125
	 * @param bool            $fatal = true if fatal is true, dies with an error message if the template cannot be found
126
	 *
127
	 * @return bool|null
128
	 * @throws Elk_Exception theme_template_error
129
	 */
130
	protected function requireTemplate($template_name, $style_sheets, $fatal)
131
	{
132
		global $context, $settings, $txt, $scripturl, $db_show_debug;
133
134
		if (!is_array($style_sheets))
135
		{
136
			$style_sheets = array($style_sheets);
137
		}
138
139
		if ($this->default_loaded === false)
140
		{
141
			loadCSSFile('index.css');
142
			$this->default_loaded = true;
143
		}
144
145
		// Any specific template style sheets to load?
146
		if (!empty($style_sheets))
147
		{
148
			trigger_error('Use of loadTemplate to add style sheets to the head is deprecated.', E_USER_DEPRECATED);
149
			$sheets = array();
150
			foreach ($style_sheets as $sheet)
151
			{
152
				$sheets[] = stripos('.css', $sheet) !== false ? $sheet : $sheet . '.css';
153
				if ($sheet == 'admin' && !empty($context['theme_variant']))
154
				{
155
					$sheets[] = $context['theme_variant'] . '/admin' . $context['theme_variant'] . '.css';
156
				}
157
			}
158
159
			loadCSSFile($sheets);
160
		}
161
162
		// No template to load?
163
		if ($template_name === false)
164
		{
165
			return true;
166
		}
167
168
		$loaded = false;
169
		$template_dir = '';
170
		foreach ($this->dirs as $template_dir)
171
		{
172
			if (file_exists($template_dir . '/' . $template_name . '.template.php'))
173
			{
174
				$loaded = true;
175
				$this->templateInclude($template_dir . '/' . $template_name . '.template.php', true);
176
				break;
177
			}
178
		}
179
180
		if ($loaded)
181
		{
182
			if ($db_show_debug === true)
183
			{
184
				Debug::instance()->add('templates', $template_name . ' (' . basename($template_dir) . ')');
185
			}
186
187
			// If they have specified an initialization function for this template, go ahead and call it now.
188
			if (function_exists('template_' . $template_name . '_init'))
189
			{
190
				call_user_func('template_' . $template_name . '_init');
191
			}
192
		}
193
		// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
194
		elseif (!file_exists($settings['default_theme_dir']) && file_exists(BOARDDIR . '/themes/default'))
195
		{
196
			$settings['default_theme_dir'] = BOARDDIR . '/themes/default';
197
			$this->addDirectory($settings['default_theme_dir']);
198
199
			if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
200
			{
201
				loadLanguage('Errors');
202
203
				if (!isset($context['security_controls_files']['title']))
204
				{
205
					$context['security_controls_files']['title'] = $txt['generic_warning'];
206
				}
207
208
				$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>';
209
			}
210
211
			loadTemplate($template_name);
212
		}
213
		// Cause an error otherwise.
214
		elseif ($template_name !== 'Errors' && $template_name !== 'index' && $fatal)
215
		{
216
			throw new Elk_Exception('theme_template_error', 'template', array((string) $template_name));
217
		}
218
		elseif ($fatal)
219
		{
220
			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
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
221
		}
222
		else
223
		{
224
			return false;
225
		}
226
227
		return true;
228
	}
229
230
	/**
231
	 * Load the template/language file using eval or require? (with eval we can show an error message!)
232
	 *
233
	 * What it does:
234
	 *
235
	 * - Loads the template or language file specified by filename.
236
	 * - Uses eval unless disableTemplateEval is enabled.
237
	 * - Outputs a parse error if the file did not exist or contained errors.
238
	 * - Attempts to detect the error and line, and show detailed information.
239
	 *
240
	 * @param string $filename
241
	 * @param bool $once = false, if true only includes the file once (like include_once)
242
	 */
243
	public function templateInclude($filename, $once = false)
244
	{
245
		// I know this looks weird but this is used to include $txt files. If the parent doesn't declare them global
246
		// the scope will be local to this function. IOW, don't remove this line!
247
		global $txt;
248
249
		// Don't include the file more than once, if $once is true.
250
		if ($once && in_array($filename, $this->templates))
251
		{
252
			return;
253
		}
254
		// Add this file to the include list, whether $once is true or not.
255
		else
256
		{
257
			$this->templates[] = $filename;
258
		}
259
260
		// Load it if we find it
261
		$file_found = file_exists($filename);
262
263
		if ($once && $file_found)
264
		{
265
			require_once($filename);
266
		}
267
		elseif ($file_found)
268
		{
269
			require($filename);
270
		}
271
272
		if ($file_found !== true)
273
		{
274
			$this->templateNotFound($filename);
275
		}
276
	}
277
278
	/**
279
	 * Displays an error when a template is not found or has syntax errors preventing its loading
280
	 *
281
	 * @param string $filename
282
	 */
283
	protected function templateNotFound($filename)
284
	{
285
		global $context, $txt, $scripturl, $modSettings, $boardurl;
286
		global $maintenance, $mtitle, $mmessage;
287
288
		obStart(!empty($modSettings['enableCompressedOutput']));
289
290
		// Don't cache error pages!!
291
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
292
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
293
		header('Cache-Control: no-cache');
294
295
		if (!isset($txt['template_parse_error']))
296
		{
297
			$txt['template_parse_error'] = 'Template Parse Error!';
298
			$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>.';
299
			$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>.';
300
			$txt['template_parse_undefined'] = 'An undefined error occurred during the parsing of this template';
301
		}
302
303
		// First, let's get the doctype and language information out of the way.
304
		echo '<!DOCTYPE html>
305
<html ', !empty($context['right_to_left']) ? 'dir="rtl"' : '', '>
306
	<head>
307
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
308
309
		if (!empty($maintenance) && !allowedTo('admin_forum'))
310
		{
311
			echo '
312
		<title>', $mtitle, '</title>
313
	</head>
314
	<body>
315
		<h3>', $mtitle, '</h3>
316
		', $mmessage, '
317
	</body>
318
</html>';
319
		}
320
		elseif (!allowedTo('admin_forum'))
321
		{
322
			echo '
323
		<title>', $txt['template_parse_error'], '</title>
324
	</head>
325
	<body>
326
		<h3>', $txt['template_parse_error'], '</h3>
327
		', $txt['template_parse_error_message'], '
328
	</body>
329
</html>';
330
		}
331
		else
332
		{
333
			require_once(SUBSDIR . '/Package.subs.php');
334
335
			$error = fetch_web_data($boardurl . strtr($filename, array(BOARDDIR => '', strtr(BOARDDIR, '\\', '/') => '')));
336
			$last_error = error_get_last();
337
			if (empty($error) && !empty($last_error['message']))
338
			{
339
				$error = $last_error['message'];
340
			}
341
			elseif (empty($error))
342
			{
343
				$error = $txt['template_parse_undefined'];
344
			}
345
346
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
347
348
			echo '
349
		<title>', $txt['template_parse_error'], '</title>
350
	</head>
351
	<body>
352
		<h3>', $txt['template_parse_error'], '</h3>
353
		', sprintf($txt['template_parse_error_details'], strtr($filename, array(BOARDDIR => '', strtr(BOARDDIR, '\\', '/') => '')), $boardurl, $scripturl . '?theme=1');
354
355
			if (!empty($error))
356
			{
357
				echo '
358
		<hr />
359
360
		<div style="margin: 0 20px;"><span style="font-family: monospace;">', strtr(strtr($error, array('<strong>' . BOARDDIR => '<strong>...', '<strong>' . strtr(BOARDDIR, '\\', '/') => '<strong>...')), '\\', '/'), '</span></div>';
361
			}
362
363
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
364
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
365
			{
366
				$data = file($filename);
367
				$data2 = highlight_php_code(implode('', $data));
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $pieces of implode() does only seem to accept array, 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

367
				$data2 = highlight_php_code(implode('', /** @scrutinizer ignore-type */ $data));
Loading history...
368
				$data2 = preg_split('~\<br( /)?\>~', $data2);
369
370
				// Fix the PHP code stuff...
371
				if (!isBrowser('gecko'))
372
				{
373
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
374
				}
375
				else
376
				{
377
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
378
				}
379
380
				// Now we get to work around a bug in PHP where it doesn't escape <br />s!
381
				$j = -1;
382
				foreach ($data as $line)
383
				{
384
					$j++;
385
386
					if (substr_count($line, '<br />') == 0)
387
					{
388
						continue;
389
					}
390
391
					$n = substr_count($line, '<br />');
392
					for ($i = 0; $i < $n; $i++)
393
					{
394
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
395
						unset($data2[$j + $i + 1]);
396
					}
397
					$j += $n;
398
				}
399
				$data2 = array_values($data2);
400
				array_unshift($data2, '');
401
402
				echo '
403
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
404
405
				// Figure out what the color coding was before...
406
				$line = max($match[1] - 9, 1);
407
				$last_line = '';
408
				for ($line2 = $line - 1; $line2 > 1; $line2--)
409
				{
410
					if (strpos($data2[$line2], '<') !== false)
411
					{
412
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
413
						{
414
							$last_line = $color_match[1];
415
						}
416
						break;
417
					}
418
				}
419
420
				// Show the relevant lines...
421
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
422
				{
423
					if ($line == $match[1])
424
					{
425
						echo '</pre><div style="background: #ffb0b5;"><pre style="margin: 0;">';
426
					}
427
428
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
429
					if (isset($data2[$line]) && $data2[$line] != '')
430
					{
431
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
432
					}
433
434
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
435
					{
436
						$last_line = $color_match[1];
437
						echo '</', substr($last_line, 1, 4), '>';
438
					}
439
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
440
					{
441
						$last_line = '';
442
					}
443
					elseif ($last_line != '' && $data2[$line] != '')
444
					{
445
						echo '</', substr($last_line, 1, 4), '>';
446
					}
447
448
					if ($line == $match[1])
449
					{
450
						echo '</pre></div><pre style="margin: 0;">';
451
					}
452
					else
453
					{
454
						echo "\n";
455
					}
456
				}
457
458
				echo '</pre></div>';
459
			}
460
461
			echo '
462
	</body>
463
</html>';
464
		}
465
466
		die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
467
	}
468
469
	/**
470
	 * Load a sub-template.
471
	 *
472
	 * What it does:
473
	 *
474
	 * - loads the sub template specified by sub_template_name, which must be in an already-loaded template.
475
	 * - if ?debug is in the query string, shows administrators a marker after every sub template
476
	 * for debugging purposes.
477
	 *
478
	 * @todo get rid of reading $_REQUEST directly
479
	 *
480
	 * @param string      $sub_template_name
481
	 * @param bool|string $fatal = false, $fatal = true is for templates that
482
	 *   shouldn't get a 'pretty' error screen 'ignore' to skip
483
	 *
484
	 * @throws Elk_Exception theme_template_error
485
	 */
486
	public function loadSubTemplate($sub_template_name, $fatal = false)
487
	{
488
		global $txt, $db_show_debug;
489
490
		if ($sub_template_name === false)
0 ignored issues
show
introduced by
The condition $sub_template_name === false is always false.
Loading history...
491
		{
492
			return;
493
		}
494
495
		if ($db_show_debug === true)
496
		{
497
			Debug::instance()->add('sub_templates', $sub_template_name);
498
		}
499
500
		// Figure out what the template function is named.
501
		$theme_function = 'template_' . $sub_template_name;
502
503
		if (function_exists($theme_function))
504
		{
505
			$this->_templateDebug($sub_template_name, true);
506
			$theme_function();
507
		}
508
		elseif ($fatal === false)
509
		{
510
			throw new Elk_Exception('theme_template_error', 'template', array((string) $sub_template_name));
511
		}
512
		elseif ($fatal !== 'ignore')
513
		{
514
			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
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
515
		}
516
517
		$this->_templateDebug($sub_template_name);
518
	}
519
520
	/**
521
	 * Are we showing debugging for templates?  Just make sure not to do it before the doctype...
522
	 *
523
	 * @param bool $start
524
	 * @param string $sub_template_name
525
	 */
526
	private function _templateDebug($sub_template_name, $start = false)
527
	{
528
		if (isset($_REQUEST['debug']) && allowedTo('admin_forum') && !in_array($sub_template_name, array('init')) && ob_get_length() > 0 && !isset($_REQUEST['xml']) && !isset($_REQUEST['api']))
529
		{
530
			echo '
531
 				<div class="warningbox">---- ', $sub_template_name, ' ', ($start ? 'starts' : 'ends'), ' ----</div>';
532
		}
533
	}
534
535
	/**
536
	 * Reloads the directory stack/queue to ensure they are searched in the proper order
537
	 *
538
	 * @param array $settings
539
	 */
540
	public function reloadDirectories(array $settings)
541
	{
542
		$this->dirs = array();
543
544
		if (!empty($settings['theme_dir']))
545
		{
546
			$this->addDirectory($settings['theme_dir']);
547
		}
548
549
		// Based on theme (if there is one).
550
		if (!empty($settings['base_theme_dir']))
551
		{
552
			$this->addDirectory($settings['base_theme_dir']);
553
		}
554
555
		// Lastly the default theme.
556
		if ($settings['theme_dir'] !== $settings['default_theme_dir'])
557
		{
558
			$this->addDirectory($settings['default_theme_dir']);
559
		}
560
	}
561
562
	/**
563
	 * Add a template directory to the search stack
564
	 *
565
	 * @param string $dir
566
	 *
567
	 * @return $this
568
	 */
569
	public function addDirectory($dir)
570
	{
571
		$this->dirs[] = (string) $dir;
572
573
		return $this;
574
	}
575
576
	/**
577
	 * Sets the directory array in to the class
578
	 *
579
	 * @param array $dirs
580
	 */
581
	public function setDirectories(array $dirs)
582
	{
583
		$this->dirs = $dirs;
584
	}
585
586
	/**
587
	 * Returns if theme directory's have been loaded
588
	 *
589
	 * @return bool
590
	 */
591
	public function hasDirectories()
592
	{
593
		return !empty($this->dirs);
594
	}
595
596
	/**
597
	 * Return the directory's that have been loaded
598
	 *
599
	 * @return array
600
	 */
601
	public function getTemplateDirectories()
602
	{
603
		return $this->dirs;
604
	}
605
606
	/**
607
	 * Return the template sheet stack
608
	 *
609
	 * @return array
610
	 */
611
	public function getIncludedTemplates()
612
	{
613
		return $this->templates;
614
	}
615
616
	/**
617
	 * Find and return Templates instance if it exists,
618
	 * or create a new instance
619
	 *
620
	 * @return Templates
621
	 */
622
	public static function instance()
623
	{
624
		if (self::$instance === null)
625
		{
626
			self::$instance = new Templates;
627
		}
628
629
		return self::$instance;
630
	}
631
}
632