Completed
Pull Request — development (#2960)
by Stephen
10:02
created

Theme::loadThemeJavascript()   D

Complexity

Conditions 9
Paths 2

Size

Total Lines 41
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 24
nc 2
nop 0
dl 0
loc 41
rs 4.909
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * The default theme
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 Release Candidate 1
15
 *
16
 */
17
18
namespace Themes\DefaultTheme;
19
20
/**
21
 * Class Theme
22
 *
23
 * - Extends the abstract theme class
24
 *
25
 * @package Themes\DefaultTheme
26
 */
27
class Theme extends \Theme
28
{
29
	/**
30
	 * This is the only template included in the sources.
31
	 */
32
	public function template_rawdata()
33
	{
34
		global $context;
35
36
		echo $context['raw_data'];
37
	}
38
39
	/**
40
	 * The header template
41
	 */
42
	public function template_header()
43
	{
44
		global $context, $settings;
45
46
		doSecurityChecks();
47
48
		$this->setupThemeContext();
49
50
		// Print stuff to prevent caching of pages (except on attachment errors, etc.)
51
		if (empty($context['no_last_modified']))
52
		{
53
			header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
54
			header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
55
56
			if ($this->headerSent('Content-Type') === false && (!isset($_REQUEST['xml']) && !isset($_REQUEST['api'])))
57
			{
58
				header('Content-Type: text/html; charset=UTF-8');
59
			}
60
		}
61
62
		if ($this->headerSent('Content-Type') === false)
63
		{
64
			// Probably temporary ($_REQUEST['xml'] should be replaced by $_REQUEST['api'])
65
			if (isset($_REQUEST['api']) && $_REQUEST['api'] === 'json')
66
			{
67
				header('Content-Type: application/json; charset=UTF-8');
68
			}
69
			elseif (isset($_REQUEST['xml']) || isset($_REQUEST['api']))
70
			{
71
				header('Content-Type: text/xml; charset=UTF-8');
72
			}
73
			else
74
			{
75
				header('Content-Type: text/html; charset=UTF-8');
76
			}
77
		}
78
79
		foreach ($this->layers->prepareContext() as $layer)
80
		{
81
			loadSubTemplate($layer . '_above', 'ignore');
82
		}
83
84 View Code Duplication
		if (isset($settings['use_default_images']) && $settings['use_default_images'] === 'defaults' && isset($settings['default_template']))
85
		{
86
			$settings['theme_url'] = $settings['default_theme_url'];
87
			$settings['images_url'] = $settings['default_images_url'];
88
			$settings['theme_dir'] = $settings['default_theme_dir'];
89
		}
90
	}
91
92
	/**
93
	 * Checks if a header of a given type has already been sent
94
	 *
95
	 * @param string $type
96
	 *
97
	 * @return bool
98
	 */
99
	protected function headerSent($type)
100
	{
101
		$sent = headers_list();
102
		foreach ($sent as $header)
103
		{
104
			if (substr($header, 0, strlen($type)) === $type)
105
			{
106
				return true;
107
			}
108
		}
109
110
		return false;
111
	}
112
113
	/**
114
	 * Show the copyright.
115
	 */
116
	public function theme_copyright()
117
	{
118
		global $forum_copyright;
119
120
		// Don't display copyright for things like SSI.
121
		if (!defined('FORUM_VERSION'))
122
		{
123
			return;
124
		}
125
126
		// Put in the version...
127
		$forum_copyright = replaceBasicActionUrl(sprintf($forum_copyright, FORUM_VERSION));
128
129
		echo '
130
					', $forum_copyright;
131
	}
132
133
	/**
134
	 * The template footer
135
	 */
136
	public function template_footer()
137
	{
138
		global $context, $settings, $modSettings, $time_start;
139
140
		$db = database();
141
142
		// Show the load time?  (only makes sense for the footer.)
143
		$context['show_load_time'] = !empty($modSettings['timeLoadPageEnable']);
144
		$context['load_time'] = round(microtime(true) - $time_start, 3);
145
		$context['load_queries'] = $db->num_queries();
146
147 View Code Duplication
		if (isset($settings['use_default_images']) && $settings['use_default_images'] === 'defaults' && isset($settings['default_template']))
148
		{
149
			$settings['theme_url'] = $settings['actual_theme_url'];
150
			$settings['images_url'] = $settings['actual_images_url'];
151
			$settings['theme_dir'] = $settings['actual_theme_dir'];
152
		}
153
154
		foreach ($this->layers->reverseLayers() as $layer)
155
		{
156
			$this->templates->loadSubTemplate($layer . '_below', 'ignore');
157
		}
158
	}
159
160
	/**
161
	 * Loads the required jQuery files for the system
162
	 *
163
	 * - Determines the correct script tags to add based on CDN/Local/Auto
164
	 */
165
	protected function templateJquery()
166
	{
167
		global $modSettings, $settings;
168
169
		// Using a specified version of jquery or what was shipped 3.1.1  / 1.12.1
170
		$jquery_version = (!empty($modSettings['jquery_default']) && !empty($modSettings['jquery_version'])) ? $modSettings['jquery_version'] : '3.1.1';
171
		$jqueryui_version = (!empty($modSettings['jqueryui_default']) && !empty($modSettings['jqueryui_version'])) ? $modSettings['jqueryui_version'] : '1.12.1';
172
173
		switch ($modSettings['jquery_source'])
174
		{
175
			// Only getting the files from the CDN?
176
			case 'cdn':
177
				echo '
178
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/' . $jquery_version . '/jquery.min.js" id="jquery"></script>',
179
				(!empty($modSettings['jquery_include_ui']) ? '
180
	<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/' . $jqueryui_version . '/jquery-ui.min.js" id="jqueryui"></script>' : '');
181
				break;
182
			// Just use the local file
183
			case 'local':
184
				echo '
185
	<script src="', $settings['default_theme_url'], '/scripts/jquery-' . $jquery_version . '.min.js" id="jquery"></script>',
186
				(!empty($modSettings['jquery_include_ui']) ? '
187
	<script src="' . $settings['default_theme_url'] . '/scripts/jquery-ui-' . $jqueryui_version . '.min.js" id="jqueryui"></script>' : '');
188
				break;
189
			// CDN with local fallback
190
			case 'auto':
191
				echo '
192
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/' . $jquery_version . '/jquery.min.js" id="jquery"></script>',
193
				(!empty($modSettings['jquery_include_ui']) ? '
194
	<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/' . $jqueryui_version . '/jquery-ui.min.js" id="jqueryui"></script>' : '');
195
				echo '
196
	<script>
197
		window.jQuery || document.write(\'<script src="', $settings['default_theme_url'], '/scripts/jquery-' . $jquery_version . '.min.js"><\/script>\');',
198
				(!empty($modSettings['jquery_include_ui']) ? '
199
		window.jQuery.ui || document.write(\'<script src="' . $settings['default_theme_url'] . '/scripts/jquery-ui-' . $jqueryui_version . '.min.js"><\/script>\')' : ''), '
200
	</script>';
201
				break;
202
		}
203
	}
204
205
	/**
206
	 * Loads the JS files that have been requested
207
	 *
208
	 * - Will combine / minify the files it the option is set.
209
	 * - Handles both above and below (deferred) files
210
	 *
211
	 * @param bool $do_deferred
212
	 */
213
	protected function templateJavascriptFiles($do_deferred)
214
	{
215
		global $modSettings, $settings;
216
217
		// Combine and minify javascript source files to save bandwidth and requests
218
		if (!empty($modSettings['minify_css_js']))
219
		{
220
			$combiner = new \Site_Combiner($settings['default_theme_cache_dir'], $settings['default_theme_cache_url']);
221
			$combine_name = $combiner->site_js_combine($this->js_files, $do_deferred);
222
223
			call_integration_hook('post_javascript_combine', array(&$combine_name, $combiner));
224
225
			if (!empty($combine_name))
226
			{
227
				echo '
228
	<script src="', $combine_name, '" id="jscombined', $do_deferred ? 'bottom' : 'top', '"></script>';
229
			}
230
			// While we have Javascript files to place in the template
231
			foreach ($combiner->getSpares() as $id => $js_file)
232
			{
233
				if ((!$do_deferred && empty($js_file['options']['defer'])) || ($do_deferred && !empty($js_file['options']['defer'])))
234
				{
235
					echo '
236
	<script src="', $js_file['filename'], '" id="', $id, '"', !empty($js_file['options']['async']) ? ' async="async"' : '', '></script>';
237
				}
238
			}
239
		}
240
		// Just give them the full load then
241
		else
242
		{
243
			// While we have Javascript files to place in the template
244
			foreach ($this->js_files as $id => $js_file)
245
			{
246
				if ((!$do_deferred && empty($js_file['options']['defer'])) || ($do_deferred && !empty($js_file['options']['defer'])))
247
				{
248
					echo '
249
	<script src="', $js_file['filename'], '" id="', $id, '"', !empty($js_file['options']['async']) ? ' async="async"' : '', '></script>';
250
				}
251
			}
252
		}
253
	}
254
255
	/**
256
	 * Deletes the hives (aggregated CSS and JS files) previously created.
257
	 *
258
	 * @param string $type = 'all' Filters the types of hives (valid values:
259
	 *                               * 'all'
260
	 *                               * 'css'
261
	 *                               * 'js'
262
	 */
263
	public function cleanHives($type = 'all')
264
	{
265
		global $settings;
266
267
		$combiner = new \Site_Combiner($settings['default_theme_cache_dir'], $settings['default_theme_cache_url']);
268
		$result = true;
269
270
		if ($type === 'all' || $type === 'css')
271
		{
272
			$result &= $combiner->removeCssHives();
273
		}
274
275
		if ($type === 'all' || $type === 'js')
276
		{
277
			$result &= $combiner->removeJsHives();
278
		}
279
280
		return $result;
281
	}
282
283
	/**
284
	 * Output the Javascript files
285
	 *
286
	 * What it does:
287
	 *
288
	 * - Tabbing in this function is to make the HTML source look proper
289
	 * - Outputs jQuery/jQueryUI from the proper source (local/CDN)
290
	 * - If deferred is set function will output all JS (source & inline) set to load at page end
291
	 * - If the admin option to combine files is set, will use Combiner.class
292
	 *
293
	 * @param bool $do_deferred = false
294
	 */
295
	public function template_javascript($do_deferred = false)
296
	{
297
		global $modSettings;
298
299
		// First up, load jQuery and jQuery UI
300
		if (isset($modSettings['jquery_source']) && !$do_deferred)
301
		{
302
			$this->templateJquery();
303
		}
304
305
		// Use this hook to work with Javascript files and vars pre output
306
		call_integration_hook('pre_javascript_output');
307
308
		// Load in the JS files
309
		if (!empty($this->js_files))
310
		{
311
			$this->templateJavascriptFiles($do_deferred);
312
		}
313
314
		// Build the declared Javascript variables script
315
		$js_vars = array();
316
		if (!empty($this->js_vars) && !$do_deferred)
317
		{
318
			foreach ($this->js_vars as $var => $value)
319
				$js_vars[] = $var . ' = ' . $value;
320
321
			// Newlines and tabs are here to make it look nice in the page source view, stripped if minimized though
322
			$this->js_inline['standard'][] = 'var ' . implode(",\n\t\t\t", $js_vars) . ';';
323
		}
324
325
		// Inline JavaScript - Actually useful some times!
326
		if (!empty($this->js_inline))
327
		{
328
			// Deferred output waits until we are deferring !
329 View Code Duplication
			if (!empty($this->js_inline['defer']) && $do_deferred)
330
			{
331
				// Combine them all in to one output
332
				$this->js_inline['defer'] = array_map('trim', $this->js_inline['defer']);
333
				$inline_defered_code = implode("\n\t\t", $this->js_inline['defer']);
334
335
				// Output the deferred script
336
				echo '
337
	<script>
338
		', $inline_defered_code, '
339
	</script>';
340
			}
341
342
			// Standard output, and our javascript vars, get output when we are not on a defered call
343 View Code Duplication
			if (!empty($this->js_inline['standard']) && !$do_deferred)
344
			{
345
				$this->js_inline['standard'] = array_map('trim', $this->js_inline['standard']);
346
347
				// And output the js vars and standard scripts to the page
348
				echo '
349
	<script>
350
		', implode("\n\t\t", $this->js_inline['standard']), '
351
	</script>';
352
			}
353
		}
354
	}
355
356
	/**
357
	 * Output the CSS files
358
	 *
359
	 * What it does:
360
	 *  - If the admin option to combine files is set, will use Combiner.class
361
	 */
362
	public function template_css()
363
	{
364
		global $modSettings, $settings;
365
366
		// Use this hook to work with CSS files pre output
367
		call_integration_hook('pre_css_output');
368
369
		// Combine and minify the CSS files to save bandwidth and requests?
370
		if (!empty($this->css_files))
371
		{
372
			if (!empty($modSettings['minify_css_js']))
373
			{
374
				$combiner = new \Site_Combiner($settings['default_theme_cache_dir'], $settings['default_theme_cache_url']);
375
				$combine_name = $combiner->site_css_combine($this->css_files);
376
377
				call_integration_hook('post_css_combine', array(&$combine_name, $combiner));
378
379
				if (!empty($combine_name))
380
				{
381
					echo '
382
	<link rel="stylesheet" href="', $combine_name, '" id="csscombined" />';
383
				}
384
385
				foreach ($combiner->getSpares() as $id => $file)
386
					echo '
387
	<link rel="stylesheet" href="', $file['filename'], '" id="', $id, '" />';
388
			}
389
			else
390
			{
391
				foreach ($this->css_files as $id => $file)
392
					echo '
393
	<link rel="stylesheet" href="', $file['filename'], '" id="', $id, '" />';
394
			}
395
		}
396
	}
397
398
	/**
399
	 * Output the inline-CSS in a style tag
400
	 */
401
	public function template_inlinecss()
402
	{
403
		$style_tag = '';
404
405
		// Combine and minify the CSS files to save bandwidth and requests?
406
		if (!empty($this->css_rules))
407
		{
408
			if (!empty($this->css_rules['all']))
409
			{
410
				$style_tag .= '
411
	' . $this->css_rules['all'];
412
			}
413
			if (!empty($this->css_rules['media']))
414
			{
415
				foreach ($this->css_rules['media'] as $key => $val)
416
				{
417
					$style_tag .= '
418
	@media ' . $key . '{
419
		' . $val . '
420
	}';
421
				}
422
			}
423
		}
424
425
		if (!empty($style_tag))
426
		{
427
			echo '
428
	<style>' . $style_tag . '
429
	</style>';
430
		}
431
	}
432
433
	/**
434
	 * Calls on template_show_error from index.template.php to show warnings
435
	 * and security errors for admins
436
	 */
437
	public function template_admin_warning_above()
438
	{
439
		global $context, $txt;
440
441 View Code Duplication
		if (!empty($context['security_controls_files']))
442
		{
443
			$context['security_controls_files']['type'] = 'serious';
444
			template_show_error('security_controls_files');
445
		}
446
447 View Code Duplication
		if (!empty($context['security_controls_query']))
448
		{
449
			$context['security_controls_query']['type'] = 'serious';
450
			template_show_error('security_controls_query');
451
		}
452
453 View Code Duplication
		if (!empty($context['security_controls_ban']))
454
		{
455
			$context['security_controls_ban']['type'] = 'serious';
456
			template_show_error('security_controls_ban');
457
		}
458
459
		if (!empty($context['new_version_updates']))
460
		{
461
			template_show_error('new_version_updates');
462
		}
463
464
		// Any special notices to remind the admin about?
465
		if (!empty($context['warning_controls']))
466
		{
467
			$context['warning_controls']['errors'] = $context['warning_controls'];
468
			$context['warning_controls']['title'] = $txt['admin_warning_title'];
469
			$context['warning_controls']['type'] = 'warning';
470
			template_show_error('warning_controls');
471
		}
472
	}
473
474
	/**
475
	 * If the option to pretty output code is on, this loads the JS and CSS
476
	 */
477
	public function addCodePrettify()
478
	{
479
		global $modSettings;
480
481
		if (!empty($modSettings['enableCodePrettify']))
482
		{
483
			loadCSSFile('prettify.css');
484
			loadJavascriptFile('prettify.min.js', array('defer' => true));
485
486
			addInlineJavascript('
487
			$(function() {
488
				prettyPrint();
489
			});', true);
490
		}
491
	}
492
493
	/**
494
	 * If video embedding is enabled, this loads the needed JS and vars
495
	 */
496
	public function autoEmbedVideo()
497
	{
498
		global $txt, $modSettings;
499
500
		if (!empty($modSettings['enableVideoEmbeding']))
501
		{
502
			addInlineJavascript('
503
			var oEmbedtext = ({
504
				preview_image : ' . JavaScriptEscape($txt['preview_image']) . ',
505
				ctp_video : ' . JavaScriptEscape($txt['ctp_video']) . ',
506
				hide_video : ' . JavaScriptEscape($txt['hide_video']) . ',
507
				youtube : ' . JavaScriptEscape($txt['youtube']) . ',
508
				vimeo : ' . JavaScriptEscape($txt['vimeo']) . ',
509
				dailymotion : ' . JavaScriptEscape($txt['dailymotion']) . '
510
			});', true);
511
512
			loadJavascriptFile('elk_jquery_embed.js', array('defer' => true));
513
		}
514
	}
515
516
	/**
517
	 * Ensures we kick the mail queue from time to time so that it gets
518
	 * checked as often as possible.
519
	 */
520
	public function doScheduledSendMail()
521
	{
522
		global $modSettings;
523
524
		if (isBrowser('possibly_robot'))
525
		{
526
			// @todo Maybe move this somewhere better?!
527
			$controller = new \ScheduledTasks_Controller();
528
529
			// What to do, what to do?!
530
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
531
			{
532
				$controller->action_autotask();
533
			}
534
			else
535
			{
536
				$controller->action_reducemailqueue();
537
			}
538
		}
539
		else
540
		{
541
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
542
			$ts = $type === 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
543
544
			addInlineJavascript('
545
		function elkAutoTask()
546
		{
547
			var tempImage = new Image();
548
			tempImage.src = elk_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '";
549
		}
550
		window.setTimeout("elkAutoTask();", 1);', true);
551
		}
552
	}
553
554
	/**
555
	 * Relative times require a few variables be set in the JS
556
	 */
557
	public function relativeTimes()
558
	{
559
		global $modSettings, $context, $txt;
560
561
		// Relative times?
562
		if (!empty($modSettings['todayMod']) && $modSettings['todayMod'] > 2)
563
		{
564
			addInlineJavascript('
565
			var oRttime = ({
566
				referenceTime : ' . forum_time() * 1000 . ',
567
				now : ' . JavaScriptEscape($txt['rt_now']) . ',
568
				minute : ' . JavaScriptEscape($txt['rt_minute']) . ',
569
				minutes : ' . JavaScriptEscape($txt['rt_minutes']) . ',
570
				hour : ' . JavaScriptEscape($txt['rt_hour']) . ',
571
				hours : ' . JavaScriptEscape($txt['rt_hours']) . ',
572
				day : ' . JavaScriptEscape($txt['rt_day']) . ',
573
				days : ' . JavaScriptEscape($txt['rt_days']) . ',
574
				week : ' . JavaScriptEscape($txt['rt_week']) . ',
575
				weeks : ' . JavaScriptEscape($txt['rt_weeks']) . ',
576
				month : ' . JavaScriptEscape($txt['rt_month']) . ',
577
				months : ' . JavaScriptEscape($txt['rt_months']) . ',
578
				year : ' . JavaScriptEscape($txt['rt_year']) . ',
579
				years : ' . JavaScriptEscape($txt['rt_years']) . ',
580
			});
581
			updateRelativeTime();', true);
582
583
			$context['using_relative_time'] = true;
584
		}
585
	}
586
587
	/**
588
	 * Sets up the basic theme context stuff.
589
	 *
590
	 * @param bool $forceload = false
591
	 */
592
	public function setupThemeContext($forceload = false)
593
	{
594
		global $modSettings, $user_info, $scripturl, $context, $settings, $options, $txt;
595
596
		static $loaded = false;
597
598
		// Under SSI this function can be called more then once.  That can cause some problems.
599
		// So only run the function once unless we are forced to run it again.
600
		if ($loaded && !$forceload)
601
		{
602
			return;
603
		}
604
605
		$loaded = true;
606
607
		$context['current_time'] = standardTime(time(), false);
608
		$context['current_action'] = isset($_GET['action']) ? $_GET['action'] : '';
609
		$context['show_quick_login'] = !empty($modSettings['enableVBStyleLogin']) && $user_info['is_guest'];
610
		$context['robot_no_index'] = in_array($context['current_action'], $this->no_index_actions);
611
612
		$bbc_parser = \BBC\ParserWrapper::getInstance();
613
614
		// Get some news...
615
		$context['news_lines'] = array_filter(explode("\n", str_replace("\r", '', trim(addslashes($modSettings['news'])))));
616
		for ($i = 0, $n = count($context['news_lines']); $i < $n; $i++)
617
		{
618
			if (trim($context['news_lines'][$i]) === '')
619
			{
620
				continue;
621
			}
622
623
			// Clean it up for presentation ;).
624
			$context['news_lines'][$i] = $bbc_parser->parseNews(stripslashes(trim($context['news_lines'][$i])));
625
		}
626
627
		// If we have some, setup for display
628
		if (!empty($context['news_lines']))
629
		{
630
			$context['random_news_line'] = $context['news_lines'][mt_rand(0, count($context['news_lines']) - 1)];
631
			$context['upper_content_callbacks'][] = 'news_fader';
632
		}
633
634
		if (!$user_info['is_guest'])
635
		{
636
			$context['user']['messages'] = &$user_info['messages'];
637
			$context['user']['unread_messages'] = &$user_info['unread_messages'];
638
			$context['user']['mentions'] = &$user_info['mentions'];
639
640
			// Personal message popup...
641
			if ($user_info['unread_messages'] > (isset($_SESSION['unread_messages']) ? $_SESSION['unread_messages'] : 0))
642
			{
643
				$context['user']['popup_messages'] = true;
644
			}
645
			else
646
			{
647
				$context['user']['popup_messages'] = false;
648
			}
649
650
			$_SESSION['unread_messages'] = $user_info['unread_messages'];
651
652
			$context['user']['avatar'] = array(
653
				'href' => !empty($user_info['avatar']['href']) ? $user_info['avatar']['href'] : '',
654
				'image' => !empty($user_info['avatar']['image']) ? $user_info['avatar']['image'] : '',
655
			);
656
657
			// Figure out how long they've been logged in.
658
			$context['user']['total_time_logged_in'] = array(
659
				'days' => floor($user_info['total_time_logged_in'] / 86400),
660
				'hours' => floor(($user_info['total_time_logged_in'] % 86400) / 3600),
661
				'minutes' => floor(($user_info['total_time_logged_in'] % 3600) / 60)
662
			);
663
		}
664
		else
665
		{
666
			$context['user']['messages'] = 0;
667
			$context['user']['unread_messages'] = 0;
668
			$context['user']['mentions'] = 0;
669
			$context['user']['avatar'] = array();
670
			$context['user']['total_time_logged_in'] = array('days' => 0, 'hours' => 0, 'minutes' => 0);
671
			$context['user']['popup_messages'] = false;
672
673
			if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1)
674
			{
675
				$txt['welcome_guest'] .= $txt['welcome_guest_activate'];
676
			}
677
678
			$txt['welcome_guest'] = replaceBasicActionUrl($txt['welcome_guest']);
679
680
			// If we've upgraded recently, go easy on the passwords.
681
			if (!empty($modSettings['enable_password_conversion']))
682
			{
683
				$context['disable_login_hashing'] = true;
684
			}
685
		}
686
687
		// Setup the main menu items.
688
		$this->setupMenuContext();
689
690
		if (empty($settings['theme_version']))
691
		{
692
			$context['show_vBlogin'] = $context['show_quick_login'];
693
		}
694
695
		// This is here because old index templates might still use it.
696
		$context['show_news'] = !empty($settings['enable_news']);
697
698
		$context['additional_dropdown_search'] = prepareSearchEngines();
699
700
		// This is done to allow theme authors to customize it as they want.
701
		$context['show_pm_popup'] = $context['user']['popup_messages'] && !empty($options['popup_messages']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] !== 'pm');
702
703
		// Add the PM popup here instead. Theme authors can still override it simply by editing/removing the 'fPmPopup' in the array.
704
		if ($context['show_pm_popup'])
705
		{
706
			addInlineJavascript('
707
			$(function() {
708
				new smc_Popup({
709
					heading: ' . JavaScriptEscape($txt['show_personal_messages_heading']) . ',
710
					content: ' . JavaScriptEscape(sprintf($txt['show_personal_messages'], $context['user']['unread_messages'], $scripturl . '?action=pm')) . ',
711
					icon: \'i-envelope\'
712
				});
713
			});', true);
714
		}
715
716
		// This looks weird, but it's because BoardIndex.controller.php references the variable.
717
		$context['common_stats']['latest_member'] = array(
718
			'id' => $modSettings['latestMember'],
719
			'name' => $modSettings['latestRealName'],
720
			'href' => $scripturl . '?action=profile;u=' . $modSettings['latestMember'],
721
			'link' => '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $modSettings['latestRealName'] . '</a>',
722
		);
723
724
		$context['common_stats'] = array(
725
			'total_posts' => comma_format($modSettings['totalMessages']),
726
			'total_topics' => comma_format($modSettings['totalTopics']),
727
			'total_members' => comma_format($modSettings['totalMembers']),
728
			'latest_member' => $context['common_stats']['latest_member'],
729
		);
730
731
		$context['common_stats']['boardindex_total_posts'] = sprintf($txt['boardindex_total_posts'], $context['common_stats']['total_posts'], $context['common_stats']['total_topics'], $context['common_stats']['total_members']);
732
733
		if (empty($settings['theme_version']))
734
		{
735
			addJavascriptVar(array('elk_scripturl' => '\'' . $scripturl . '\''));
736
		}
737
738
		if (!isset($context['page_title']))
739
		{
740
			$context['page_title'] = '';
741
		}
742
743
		// Set some specific vars.
744
		$context['page_title_html_safe'] = \Util::htmlspecialchars(un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
745
746
		// 1.0 backward compatibility: if you already put the icon in the theme dir
747
		// use that one, otherwise the default
748
		// @deprecated since 1.1
749
		if (file_exists($scripturl . '/mobile.png'))
750
		{
751
			$context['favicon'] = $scripturl . '/mobile.png';
752
		}
753
		else
754
		{
755
			$context['favicon'] = $settings['images_url'] . '/mobile.png';
756
		}
757
758
		// Since it's nice to have avatars all of the same size, and in some cases the size detection may fail,
759
		// let's add the css in any case
760
		if (!isset($context['html_headers']))
761
			$context['html_headers'] = '';
762
763
		if (!empty($modSettings['avatar_max_width']) || !empty($modSettings['avatar_max_height']))
764
		{
765
			$this->addCSSRules('
766
		.avatarresize {' . (!empty($modSettings['avatar_max_width']) ? '
767
			max-width:' . $modSettings['avatar_max_width'] . 'px;' : '') . (!empty($modSettings['avatar_max_height']) ? '
768
			max-height:' . $modSettings['avatar_max_height'] . 'px;' : '') . '
769
		}');
770
		}
771
772
		// Save some database hits, if a width for multiple wrappers is set in admin.
773
		if (!empty($settings['forum_width']))
774
		{
775
			$this->addCSSRules('
776
		.wrapper {width: ' . $settings['forum_width'] . ';}');
777
		}
778
	}
779
780
	/**
781
	 * Adds required support CSS files.
782
	 */
783
	public function loadSupportCSS()
784
	{
785
		global $modSettings, $settings;
786
787
		// Load the SVG support file with fallback to default theme
788
		loadCSSFile('icons_svg.css');
789
790
		// Load a base theme custom CSS file?
791
		if (file_exists($settings['theme_dir'] . '/css/custom.css'))
792
		{
793
			loadCSSFile('custom.css', array('fallback' => false));
794
		}
795
796
		// Load font Awesome fonts, @deprecated in 1.1 and will be removed in 2.0
797
		if (!empty($settings['require_font-awesome']) || !empty($modSettings['require_font-awesome']))
798
		{
799
			loadCSSFile('font-awesome.min.css');
800
		}
801
	}
802
803
	/**
804
	 * Sets up all of the top menu buttons
805
	 *
806
	 * What it does:
807
	 *
808
	 * - Defines every master item in the menu, as well as any sub-items
809
	 * - Ensures the chosen action is set so the menu is highlighted
810
	 * - Saves them in the cache if it is available and on
811
	 * - Places the results in $context
812
	 */
813
	public function setupMenuContext()
814
	{
815
		global $context, $modSettings, $user_info, $settings;
816
817
		// Set up the menu privileges.
818
		$context['allow_search'] = !empty($modSettings['allow_guestAccess']) ? allowedTo('search_posts') : (!$user_info['is_guest'] && allowedTo('search_posts'));
819
		$context['allow_admin'] = allowedTo(array('admin_forum', 'manage_boards', 'manage_permissions', 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', 'manage_attachments', 'manage_smileys'));
820
		$context['allow_edit_profile'] = !$user_info['is_guest'] && allowedTo(array('profile_view_own', 'profile_view_any', 'profile_identity_own', 'profile_identity_any', 'profile_extra_own', 'profile_extra_any', 'profile_remove_own', 'profile_remove_any', 'moderate_forum', 'manage_membergroups', 'profile_title_own', 'profile_title_any'));
821
		$context['allow_memberlist'] = allowedTo('view_mlist');
822
		$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
823
		$context['allow_moderation_center'] = $context['user']['can_mod'];
824
		$context['allow_pm'] = allowedTo('pm_read');
825
826
		if ($context['allow_search'])
827
		{
828
			$context['theme_header_callbacks'] = elk_array_insert($context['theme_header_callbacks'], 'login_bar', array('search_bar'), 'after');
829
		}
830
831
		$cacheTime = $modSettings['lastActive'] * 60;
832
833
		// Update the Moderation menu items with action item totals
834
		if ($context['allow_moderation_center'])
835
		{
836
			// Get the numbers for the menu ...
837
			require_once(SUBSDIR . '/Moderation.subs.php');
838
			$menu_count = loadModeratorMenuCounts();
839
		}
840
841
		$menu_count['unread_messages'] = $context['user']['unread_messages'];
0 ignored issues
show
Bug introduced by
The variable $menu_count does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
842
		$menu_count['mentions'] = $context['user']['mentions'];
843
844
		if (!empty($user_info['avatar']['href']))
845
		{
846
			$this->addCSSRules('
847
	.i-account:before {
848
		content: "";
849
		background-image: url("' . $user_info['avatar']['href'] . '");
850
	}');
851
		}
852
853
		// All the buttons we can possible want and then some, try pulling the final list of buttons from cache first.
854
		if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated'])
855
		{
856
			// Start things up: this is what we know by default
857
			require_once(SUBSDIR . '/Menu.subs.php');
858
			$buttons = loadDefaultMenuButtons();
859
860
			// Allow editing menu buttons easily.
861
			call_integration_hook('integrate_menu_buttons', array(&$buttons, &$menu_count));
862
863
			// Now we put the buttons in the context so the theme can use them.
864
			$menu_buttons = array();
865
			foreach ($buttons as $act => $button)
866
			{
867
				if (!empty($button['show']))
868
				{
869
					$button['active_button'] = false;
870
871
					// This button needs some action.
872
					if (isset($button['action_hook']))
873
					{
874
						$needs_action_hook = true;
875
					}
876
877
					if (isset($button['counter']) && !empty($menu_count[$button['counter']]))
878
					{
879
						$button['alttitle'] = $button['title'] . ' [' . $menu_count[$button['counter']] . ']';
880 View Code Duplication
						if (!empty($settings['menu_numeric_notice'][0]))
881
						{
882
							$button['title'] .= sprintf($settings['menu_numeric_notice'][0], $menu_count[$button['counter']]);
883
							$button['indicator'] = true;
884
						}
885
					}
886
887
					// Go through the sub buttons if there are any.
888
					if (isset($button['sub_buttons']))
889
					{
890
						foreach ($button['sub_buttons'] as $key => $subbutton)
891
						{
892
							if (empty($subbutton['show']))
893
							{
894
								unset($button['sub_buttons'][$key]);
895
							}
896
							elseif (isset($subbutton['counter']) && !empty($menu_count[$subbutton['counter']]))
897
							{
898
								$button['sub_buttons'][$key]['alttitle'] = $subbutton['title'] . ' [' . $menu_count[$subbutton['counter']] . ']';
899 View Code Duplication
								if (!empty($settings['menu_numeric_notice'][1]))
900
								{
901
									$button['sub_buttons'][$key]['title'] .= sprintf($settings['menu_numeric_notice'][1], $menu_count[$subbutton['counter']]);
902
								}
903
904
								// 2nd level sub buttons next...
905
								if (isset($subbutton['sub_buttons']))
906
								{
907
									foreach ($subbutton['sub_buttons'] as $key2 => $subbutton2)
908
									{
909
										$button['sub_buttons'][$key]['sub_buttons'][$key2] = $subbutton2;
910
										if (empty($subbutton2['show']))
911
										{
912
											unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
913
										}
914
										elseif (isset($subbutton2['counter']) && !empty($menu_count[$subbutton2['counter']]))
915
										{
916
											$button['sub_buttons'][$key]['sub_buttons'][$key2]['alttitle'] = $subbutton2['title'] . ' [' . $menu_count[$subbutton2['counter']] . ']';
917
											if (!empty($settings['menu_numeric_notice'][2]))
918
											{
919
												$button['sub_buttons'][$key]['sub_buttons'][$key2]['title'] .= sprintf($settings['menu_numeric_notice'][2], $menu_count[$subbutton2['counter']]);
920
											}
921
											unset($menu_count[$subbutton2['counter']]);
922
										}
923
									}
924
								}
925
							}
926
						}
927
					}
928
929
					$menu_buttons[$act] = $button;
930
				}
931
			}
932
933
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
934
			{
935
				cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime);
936
			}
937
		}
938
939
		if (!empty($menu_buttons['profile']['sub_buttons']['logout']))
940
		{
941
			$menu_buttons['profile']['sub_buttons']['logout']['href'] .= ';' . $context['session_var'] . '=' . $context['session_id'];
942
		}
943
944
		$context['menu_buttons'] = $menu_buttons;
945
946
		// Figure out which action we are doing so we can set the active tab.
947
		// Default to home.
948
		$current_action = 'home';
949
950
		if (isset($context['menu_buttons'][$context['current_action']]))
951
		{
952
			$current_action = $context['current_action'];
953
		}
954
		elseif ($context['current_action'] === 'profile')
955
		{
956
			$current_action = 'pm';
957
		}
958
		elseif ($context['current_action'] === 'theme')
959
		{
960
			$current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] === 'pick' ? 'profile' : 'admin';
961
		}
962
		elseif ($context['current_action'] === 'login2' || ($user_info['is_guest'] && $context['current_action'] === 'reminder'))
963
		{
964
			$current_action = 'login';
965
		}
966
		elseif ($context['current_action'] === 'groups' && $context['allow_moderation_center'])
967
		{
968
			$current_action = 'moderate';
969
		}
970
		elseif ($context['current_action'] === 'moderate' && $context['allow_admin'])
971
		{
972
			$current_action = 'admin';
973
		}
974
975
		// Not all actions are simple.
976
		if (!empty($needs_action_hook))
977
		{
978
			call_integration_hook('integrate_current_action', array(&$current_action));
979
		}
980
981
		if (isset($context['menu_buttons'][$current_action]))
982
		{
983
			$context['menu_buttons'][$current_action]['active_button'] = true;
984
		}
985
	}
986
987
	/**
988
	 * Load the base JS that gives Elkarte a nice rack
989
	 */
990
	public function loadThemeJavascript()
991
	{
992
		global $settings, $context, $modSettings, $scripturl, $txt, $options;
993
994
		// Queue our Javascript
995
		loadJavascriptFile(array('elk_jquery_plugins.js', 'script.js', 'script_elk.js', 'theme.js'));
996
997
		// Default JS variables for use in every theme
998
		$this->addJavascriptVar(array(
999
				'elk_theme_url' => JavaScriptEscape($settings['theme_url']),
1000
				'elk_default_theme_url' => JavaScriptEscape($settings['default_theme_url']),
1001
				'elk_images_url' => JavaScriptEscape($settings['images_url']),
1002
				'elk_smiley_url' => JavaScriptEscape($modSettings['smileys_url']),
1003
				'elk_scripturl' => '\'' . $scripturl . '\'',
1004
				'elk_iso_case_folding' => detectServer()->is('iso_case_folding') ? 'true' : 'false',
1005
				'elk_charset' => '"UTF-8"',
1006
				'elk_session_id' => JavaScriptEscape($context['session_id']),
1007
				'elk_session_var' => JavaScriptEscape($context['session_var']),
1008
				'elk_member_id' => $context['user']['id'],
1009
				'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
1010
				'ajax_notification_cancel_text' => JavaScriptEscape($txt['modify_cancel']),
1011
				'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
1012
				'use_click_menu' => !empty($options['use_click_menu']) ? 'true' : 'false',
1013
				'todayMod' => !empty($modSettings['todayMod']) ? (int) $modSettings['todayMod'] : 0)
1014
		);
1015
1016
		// Auto video embedding enabled, then load the needed JS
1017
		$this->autoEmbedVideo();
1018
1019
		// Prettify code tags? Load the needed JS and CSS.
1020
		$this->addCodePrettify();
1021
1022
		// Relative times for posts?
1023
		$this->relativeTimes();
1024
1025
		// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
1026
		if ((!empty($modSettings['mail_next_send']) && $modSettings['mail_next_send'] < time() && empty($modSettings['mail_queue_use_cron'])) || empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
1027
		{
1028
			$this->doScheduledSendMail();
1029
		}
1030
	}
1031
1032
	/**
1033
	 * Makes the default layers and languages available
1034
	 *
1035
	 * - Loads index and addon language files as needed
1036
	 * - Loads xml, index or no templates as needed
1037
	 * - Loads templates as defined by $settings['theme_templates']
1038
	 */
1039
	public function loadDefaultLayers()
1040
	{
1041
		global $settings;
1042
1043
		$simpleActions = array(
1044
			'quickhelp',
1045
			'printpage',
1046
			'quotefast',
1047
			'spellcheck',
1048
		);
1049
1050
		call_integration_hook('integrate_simple_actions', array(&$simpleActions));
1051
1052
		// Output is fully XML
1053
		if (isset($_REQUEST['xml']))
1054
		{
1055
			loadLanguage('index+Addons');
1056
1057
			// @todo added because some $settings in template_init are necessary even in
1058
			// xml mode. Maybe move template_init to a settings file?
1059
			$this->templates->load('index');
1060
			$this->templates->load('Xml');
1061
			$this->layers->removeAll();
1062
		}
1063
		// These actions don't require the index template at all.
1064
		elseif (!empty($_REQUEST['action']) && in_array($_REQUEST['action'], $simpleActions))
1065
		{
1066
			loadLanguage('index+Addons');
1067
			$this->layers->removeAll();
1068
		}
1069
		else
1070
		{
1071
			// Custom templates to load, or just default?
1072 View Code Duplication
			if (isset($settings['theme_templates']))
1073
			{
1074
				$templates = explode(',', $settings['theme_templates']);
1075
			}
1076
			else
1077
			{
1078
				$templates = array('index');
1079
			}
1080
1081
			// Load each template...
1082
			foreach ($templates as $template)
1083
				$this->templates->load($template);
1084
1085
			// ...and attempt to load their associated language files.
1086
			$required_files = implode('+', array_merge($templates, array('Addons')));
1087
			loadLanguage($required_files, '', false);
1088
1089
			// Custom template layers?
1090
			if (isset($settings['theme_layers']))
1091
			{
1092
				$layers = explode(',', $settings['theme_layers']);
1093
			}
1094
			else
1095
			{
1096
				$layers = array('html', 'body');
1097
			}
1098
1099
			$template_layers = \Template_Layers::getInstance(true);
1100
			foreach ($layers as $layer)
1101
			{
1102
				$template_layers->addBegin($layer);
1103
			}
1104
		}
1105
	}
1106
1107
	/**
1108
	 * If a variant CSS is needed, this loads it
1109
	 */
1110
	public function loadThemeVariant()
1111
	{
1112
		global $context, $settings, $options;
1113
1114
		// Overriding - for previews and that ilk.
1115
		if (!empty($_REQUEST['variant']))
1116
		{
1117
			$_SESSION['id_variant'] = $_REQUEST['variant'];
1118
		}
1119
1120
		// User selection?
1121
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
1122
		{
1123
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
1124
		}
1125
1126
		// If not a user variant, select the default.
1127
		if ($context['theme_variant'] === '' || !in_array($context['theme_variant'], $settings['theme_variants']))
1128
		{
1129
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
1130
		}
1131
1132
		// Do this to keep things easier in the templates.
1133
		$context['theme_variant'] = '_' . $context['theme_variant'];
1134
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
1135
1136
		// The most efficient way of writing multi themes is to use a master index.css plus variant.css files.
1137
		if (!empty($context['theme_variant']))
1138
		{
1139
			loadCSSFile($context['theme_variant'] . '/index' . $context['theme_variant'] . '.css');
1140
1141
			// Variant icon definitions?
1142 View Code Duplication
			if (file_exists($settings['theme_dir'] . '/css/' . $context['theme_variant'] . '/icons_svg' . $context['theme_variant'] . '.css'))
1143
			{
1144
				loadCSSFile($context['theme_variant'] .  '/icons_svg' . $context['theme_variant'] . '.css');
1145
			}
1146
1147
			// Load a theme variant custom CSS
1148 View Code Duplication
			if (!empty($context['theme_variant']) && file_exists($settings['theme_dir'] . '/css/' . $context['theme_variant'] . '/custom' . $context['theme_variant'] . '.css'))
1149
			{
1150
				loadCSSFile($context['theme_variant'] . '/custom' . $context['theme_variant'] . '.css');
1151
			}
1152
		}
1153
	}
1154
}
1155