Completed
Push — release-2.1 ( 402af6...9e40dc )
by Mert
07:25
created

Load.php ➔ getLanguages()   F

Complexity

Conditions 32
Paths 513

Size

Total Lines 112
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 32
eloc 53
c 0
b 0
f 0
nc 513
nop 2
dl 0
loc 112
rs 2.7857

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
/**
4
 * This file has the hefty job of loading information for the forum.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2016 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 3
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Load the $modSettings array.
21
 */
22
function reloadSettings()
23
{
24
	global $modSettings, $boarddir, $smcFunc, $txt, $db_character_set;
25
	global $cache_enable, $sourcedir, $context;
26
27
	// Most database systems have not set UTF-8 as their default input charset.
28
	if (!empty($db_character_set))
29
		$smcFunc['db_query']('set_character_set', '
30
			SET NAMES ' . $db_character_set,
31
			array(
32
			)
33
		);
34
35
	// Try to load it from the cache first; it'll never get cached if the setting is off.
36
	if (($modSettings = cache_get_data('modSettings', 90)) == null)
37
	{
38
		$request = $smcFunc['db_query']('', '
39
			SELECT variable, value
40
			FROM {db_prefix}settings',
41
			array(
42
			)
43
		);
44
		$modSettings = array();
45
		if (!$request)
46
			display_db_error();
47 View Code Duplication
		while ($row = $smcFunc['db_fetch_row']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
48
			$modSettings[$row[0]] = $row[1];
49
		$smcFunc['db_free_result']($request);
50
51
		// Do a few things to protect against missing settings or settings with invalid values...
52 View Code Duplication
		if (empty($modSettings['defaultMaxTopics']) || $modSettings['defaultMaxTopics'] <= 0 || $modSettings['defaultMaxTopics'] > 999)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
53
			$modSettings['defaultMaxTopics'] = 20;
54 View Code Duplication
		if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
55
			$modSettings['defaultMaxMessages'] = 15;
56 View Code Duplication
		if (empty($modSettings['defaultMaxMembers']) || $modSettings['defaultMaxMembers'] <= 0 || $modSettings['defaultMaxMembers'] > 999)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
57
			$modSettings['defaultMaxMembers'] = 30;
58 View Code Duplication
		if (empty($modSettings['defaultMaxListItems']) || $modSettings['defaultMaxListItems'] <= 0 || $modSettings['defaultMaxListItems'] > 999)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
59
			$modSettings['defaultMaxListItems'] = 15;
60
61
		if (!is_array($modSettings['attachmentUploadDir']))
62
			$modSettings['attachmentUploadDir'] = smf_json_decode($modSettings['attachmentUploadDir'], true);
63
64
		if (!empty($cache_enable))
65
			cache_put_data('modSettings', $modSettings, 90);
66
	}
67
68
	$modSettings['cache_enable'] = $cache_enable;
69
70
	// UTF-8 ?
71
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
72
73
	// Set a list of common functions.
74
	$ent_list = empty($modSettings['disableEntityCheck']) ? '&(#\d{1,7}|quot|amp|lt|gt|nbsp);' : '&(#021|quot|amp|lt|gt|nbsp);';
75
	$ent_check = empty($modSettings['disableEntityCheck']) ? function ($string)
76
		{
77
			$string = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $string);
78
			return $string;
79
		} : function ($string)
80
		{
81
			return $string;
82
		};
83
	$fix_utf8mb4 = function ($string) use ($utf8)
84
	{
85
		if (!$utf8)
86
			return $string;
87
88
		$i = 0;
89
		$len = strlen($string);
90
		$new_string = '';
91
		while ($i < $len)
92
		{
93
			$ord = ord($string[$i]);
94
			if ($ord < 128)
95
			{
96
				$new_string .= $string[$i];
97
				$i++;
98
			}
99 View Code Duplication
			elseif ($ord < 224)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
100
			{
101
				$new_string .= $string[$i] . $string[$i+1];
102
				$i += 2;
103
			}
104 View Code Duplication
			elseif ($ord < 240)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105
			{
106
				$new_string .= $string[$i] . $string[$i+1] . $string[$i+2];
107
				$i += 3;
108
			}
109
			elseif ($ord < 248)
110
			{
111
				// Magic happens.
112
				$val = (ord($string[$i]) & 0x07) << 18;
113
				$val += (ord($string[$i+1]) & 0x3F) << 12;
114
				$val += (ord($string[$i+2]) & 0x3F) << 6;
115
				$val += (ord($string[$i+3]) & 0x3F);
116
				$new_string .= '&#' . $val . ';';
117
				$i += 4;
118
			}
119
		}
120
		return $new_string;
121
	};
122
123
	// Preg_replace space characters depend on the character set in use
124
	$space_chars = $utf8 ? '\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}' : '\x00-\x08\x0B\x0C\x0E-\x19\xA0';
125
126
	// global array of anonymous helper functions, used mostly to properly handle multi byte strings
127
	$smcFunc += array(
128
		'entity_fix' => function ($string)
129
		{
130
			$num = $string[0] === 'x' ? hexdec(substr($string, 1)) : (int) $string;
131
			return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? '' : '&#' . $num . ';';
132
		},
133
		'htmlspecialchars' => function ($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') use ($ent_check, $utf8, $fix_utf8mb4)
134
		{
135
			return $fix_utf8mb4($ent_check(htmlspecialchars($string, $quote_style, $utf8 ? 'UTF-8' : $charset)));
136
		},
137
		'htmltrim' => function ($string) use ($utf8, $space_chars, $ent_check)
138
		{
139
			return preg_replace('~^(?:[ \t\n\r\x0B\x00' . $space_chars . ']|&nbsp;)+|(?:[ \t\n\r\x0B\x00' . $space_chars . ']|&nbsp;)+$~' . ($utf8 ? 'u' : ''), '', $ent_check($string));
140
		},
141
		'strlen' => function ($string) use ($ent_list, $utf8, $ent_check)
142
		{
143
			return strlen(preg_replace('~' . $ent_list . ($utf8 ? '|.~u' : '~'), '_', $ent_check($string)));
144
		},
145
		'strpos' => function ($haystack, $needle, $offset = 0) use ($utf8, $ent_check, $modSettings)
146
		{
147
			$haystack_arr = preg_split('~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|&quot;|&amp;|&lt;|&gt;|&nbsp;|.)~' . ($utf8 ? 'u' : ''), $ent_check($haystack), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
148
149
			if (strlen($needle) === 1)
150
			{
151
				$result = array_search($needle, array_slice($haystack_arr, $offset));
152
				return is_int($result) ? $result + $offset : false;
153
			}
154
			else
155
			{
156
				$needle_arr = preg_split('~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|&quot;|&amp;|&lt;|&gt;|&nbsp;|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($needle), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
157
				$needle_size = count($needle_arr);
158
159
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
160
				while ((int) $result === $result)
161
				{
162
					$offset += $result;
163
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
164
						return $offset;
165
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
166
				}
167
				return false;
168
			}
169
		},
170
		'substr' => function ($string, $start, $length = null) use ($utf8, $ent_check, $modSettings)
171
		{
172
			$ent_arr = preg_split('~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|&quot;|&amp;|&lt;|&gt;|&nbsp;|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($string), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
173
			return $length === null ? implode('', array_slice($ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
174
		},
175 View Code Duplication
		'strtolower' => $utf8 ? function ($string) use ($sourcedir)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
		{
177
			if (!function_exists('mb_strtolower'))
178
			{
179
				require_once($sourcedir . '/Subs-Charset.php');
180
				return utf8_strtolower($string);
181
			}
182
183
			return mb_strtolower($string, 'UTF-8');
184
		} : 'strtolower',
185
		'strtoupper' => $utf8 ? function ($string)
186
		{
187
			global $sourcedir;
188
189
			if (!function_exists('mb_strtolower'))
190
			{
191
				require_once($sourcedir . '/Subs-Charset.php');
192
				return utf8_strtoupper($string);
193
			}
194
195
			return mb_strtoupper($string, 'UTF-8');
196
		} : 'strtoupper',
197
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
198
		{
199
			$string = $ent_check($string);
200
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~'.  ($utf8 ? 'u' : ''), $string, $matches);
201
			$string = $matches[0];
202
			while (strlen($string) > $length)
203
				$string = preg_replace('~(?:' . $ent_list . '|.)$~'.  ($utf8 ? 'u' : ''), '', $string);
204
			return $string;
205
		},
206
		'ucfirst' => $utf8 ? function ($string) use (&$smcFunc)
207
		{
208
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
209
		} : 'ucfirst',
210
		'ucwords' => $utf8 ? function ($string) use (&$smcFunc)
211
		{
212
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
213
			for ($i = 0, $n = count($words); $i < $n; $i += 2)
214
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
215
			return implode('', $words);
216
		} : 'ucwords',
217
	);
218
219
	// Setting the timezone is a requirement for some functions.
220
	if (isset($modSettings['default_timezone']))
221
		date_default_timezone_set($modSettings['default_timezone']);
222
223
	// Check the load averages?
224
	if (!empty($modSettings['loadavg_enable']))
225
	{
226
		if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null)
227
		{
228
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
229
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
230
				$modSettings['load_average'] = (float) $matches[1];
231 View Code Duplication
			elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
232
				$modSettings['load_average'] = (float) $matches[1];
233
			else
234
				unset($modSettings['load_average']);
235
236
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
237
				cache_put_data('loadavg', $modSettings['load_average'], 90);
238
		}
239
240
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
241
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
242
243
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
244
			display_loadavg_error();
245
	}
246
247
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
248
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
249
250
	// Here to justify the name of this function. :P
251
	// It should be added to the install and upgrade scripts.
252
	// But since the converters need to be updated also. This is easier.
253
	if (empty($modSettings['currentAttachmentUploadDir']))
254
	{
255
		updateSettings(array(
256
			'attachmentUploadDir' => json_encode(array(1 => $modSettings['attachmentUploadDir'])),
257
			'currentAttachmentUploadDir' => 1,
258
		));
259
	}
260
261
	// Integration is cool.
262
	if (defined('SMF_INTEGRATION_SETTINGS'))
263
	{
264
		$integration_settings = smf_json_decode(SMF_INTEGRATION_SETTINGS, true);
265
		foreach ($integration_settings as $hook => $function)
266
			add_integration_function($hook, $function, '', false);
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
267
	}
268
269
	// Any files to pre include?
270 View Code Duplication
	if (!empty($modSettings['integrate_pre_include']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
271
	{
272
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
273
		foreach ($pre_includes as $include)
274
		{
275
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
276
			if (file_exists($include))
277
				require_once($include);
278
		}
279
	}
280
281
	// This determines the server... not used in many places, except for login fixing.
282
	$context['server'] = array(
283
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
284
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
285
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
286
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
287
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
288
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
289
		'is_windows' => strpos(PHP_OS, 'WIN') === 0,
290
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
291
	);
292
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
293
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
294
295
	// Define a list of icons used across multiple places.
296
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
297
298
	// Define an array for custom profile fields placements.
299
	$context['cust_profile_fields_placement'] = array(
300
		'standard',
301
		'icons',
302
		'above_signature',
303
		'below_signature',
304
		'below_avatar',
305
		'above_member',
306
		'bottom_poster',
307
	);
308
309
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
310
	$context['meta_tags'] = array();
311
312
	// Define an array of allowed HTML tags.
313
	$context['allowed_html_tags'] = array(
314
		'<img>',
315
		'<div>',
316
	);
317
318
	// These are the only valid image types for SMF, by default anyway.
319
	$context['validImageTypes'] = array(
320
		1 => 'gif',
321
		2 => 'jpeg',
322
		3 => 'png',
323
		5 => 'psd',
324
		6 => 'bmp',
325
		7 => 'tiff',
326
		8 => 'tiff',
327
		9 => 'jpeg',
328
		14 => 'iff'
329
	);
330
331
	// Define a list of allowed tags for descriptions.
332
	$context['description_allowed_tags'] = array('abbr', 'anchor', 'b', 'center', 'color', 'font', 'hr', 'i', 'img', 'iurl', 'left', 'li', 'list', 'ltr', 'pre', 'right', 's', 'sub', 'sup', 'table', 'td', 'tr', 'u', 'url',);
333
334
	// Get an error count, if necessary
335
	if (!isset($context['num_errors']))
336
	{
337
		$query = $smcFunc['db_query']('', '
338
			SELECT COUNT(id_error)
339
			FROM {db_prefix}log_errors',
340
			array()
341
		);
342
343
		list($context['num_errors']) = $smcFunc['db_fetch_row']($query);
344
		$smcFunc['db_free_result']($query);
345
	}
346
347
	// Call pre load integration functions.
348
	call_integration_hook('integrate_pre_load');
349
}
350
351
/**
352
 * Load all the important user information.
353
 * What it does:
354
 * 	- sets up the $user_info array
355
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
356
 * 	- first checks for cookie or integration validation.
357
 * 	- uses the current session if no integration function or cookie is found.
358
 * 	- checks password length, if member is activated and the login span isn't over.
359
 * 		- if validation fails for the user, $id_member is set to 0.
360
 * 		- updates the last visit time when needed.
361
 */
362
function loadUserSettings()
363
{
364
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
365
	global $cookiename, $user_info, $language, $context, $image_proxy_enabled, $image_proxy_secret, $boardurl;
366
367
	// Check first the integration, then the cookie, and last the session.
368
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
369
	{
370
		$id_member = 0;
371
		foreach ($integration_ids as $integration_id)
372
		{
373
			$integration_id = (int) $integration_id;
374
			if ($integration_id > 0)
375
			{
376
				$id_member = $integration_id;
377
				$already_verified = true;
378
				break;
379
			}
380
		}
381
	}
382
	else
383
		$id_member = 0;
384
385
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
386
	{
387
		$cookie_data = smf_json_decode($_COOKIE[$cookiename], true, false);
388
389
		if (empty($cookie_data))
390
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
391
392
		list ($id_member, $password) = $cookie_data;
393
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
394
	}
395
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
396
	{
397
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
398
		$cookie_data = smf_json_decode($_SESSION['login_' . $cookiename]);
399
400
		if (empty($cookie_data))
401
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
402
403
		list ($id_member, $password, $login_span) = $cookie_data;
404
		$id_member = !empty($id_member) && strlen($password) == 128 && $login_span > time() ? (int) $id_member : 0;
405
	}
406
407
	// Only load this stuff if the user isn't a guest.
408
	if ($id_member != 0)
409
	{
410
		// Is the member data cached?
411
		if (empty($modSettings['cache_enable']) || $modSettings['cache_enable'] < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
412
		{
413
			$request = $smcFunc['db_query']('', '
414
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
415
				FROM {db_prefix}members AS mem
416
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
417
				WHERE mem.id_member = {int:id_member}
418
				LIMIT 1',
419
				array(
420
					'id_member' => $id_member,
421
				)
422
			);
423
			$user_settings = $smcFunc['db_fetch_assoc']($request);
424
			$smcFunc['db_free_result']($request);
425
426 View Code Duplication
			if (!empty($modSettings['force_ssl']) && $image_proxy_enabled && stripos($user_settings['avatar'], 'http://') !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
427
				$user_settings['avatar'] = strtr($boardurl, array('http://' => 'https://')) . '/proxy.php?request=' . urlencode($user_settings['avatar']) . '&hash=' . md5($user_settings['avatar'] . $image_proxy_secret);
428
429
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
430
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
431
		}
432
433
		// Did we find 'im?  If not, junk it.
434
		if (!empty($user_settings))
435
		{
436
			// As much as the password should be right, we can assume the integration set things up.
437
			if (!empty($already_verified) && $already_verified === true)
438
				$check = true;
439
			// SHA-512 hash should be 128 characters long.
440
			elseif (strlen($password) == 128)
0 ignored issues
show
Bug introduced by
The variable $password 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...
441
				$check = hash_salt($user_settings['passwd'], $user_settings['password_salt']) == $password;
442
			else
443
				$check = false;
444
445
			// Wrong password or not activated - either way, you're going nowhere.
446
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
447
		}
448
		else
449
			$id_member = 0;
450
451
		// If we no longer have the member maybe they're being all hackey, stop brute force!
452
		if (!$id_member)
453
		{
454
			require_once($sourcedir . '/LogInOut.php');
455
			validatePasswordFlood(!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member, !empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false, $id_member != 0);
456
		}
457
		// Validate for Two Factor Authentication
458
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
459
		{
460
			$tfacookie = $cookiename . '_tfa';
461
			$tfasecret = null;
462
463
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
464
465
			if (empty($verified) || !in_array(true, $verified))
466
			{
467
				if (!empty($_COOKIE[$tfacookie]))
468
				{
469
					$tfa_data = smf_json_decode($_COOKIE[$tfacookie]);
470
471
					if (is_null($tfa_data))
472
						$tfa_data = safe_unserialize($_COOKIE[$tfacookie]);
473
474
					list ($tfamember, $tfasecret) = $tfa_data;
475
476
					if ((int) $tfamember != $id_member)
477
						$tfasecret = null;
478
				}
479
480
				if (empty($tfasecret) || hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']) != $tfasecret)
481
				{
482
					$id_member = 0;
483
					redirectexit('action=logintfa');
484
				}
485
			}
486
		}
487
		// When authenticating their two factor code, make sure to reset their ID for security
488
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
489
		{
490
			$id_member = 0;
491
			$context['tfa_member'] = $user_settings;
492
			$user_settings = array();
493
		}
494
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
495
		elseif (!empty($modSettings['tfa_mode']) && $modSettings['tfa_mode'] >= 2 && $id_member && empty($user_settings['tfa_secret']))
496
		{
497
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
498
			{
499
				//Build an array of ALL user membergroups.
500
				$full_groups = array($user_settings['id_group']);
501
				if (!empty($user_settings['additional_groups']))
502
				{
503
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
504
					$full_groups = array_unique($full_groups); //duplicates, maybe?
505
				}
506
507
				//Find out if any group requires 2FA
508
				$request = $smcFunc['db_query']('', '
509
					SELECT COUNT(id_group) AS total
510
					FROM {db_prefix}membergroups
511
					WHERE tfa_required = {int:tfa_required}
512
						AND id_group IN ({array_int:full_groups})',
513
					array(
514
						'tfa_required' => 1,
515
						'full_groups' => $full_groups,
516
					)
517
				);
518
				$row = $smcFunc['db_fetch_assoc']($request);
519
				$smcFunc['db_free_result']($request);
520
			}
521
			else
522
				$row['total'] = 1; //simplifies logics in the next "if"
0 ignored issues
show
Coding Style Comprehensibility introduced by
$row was never initialized. Although not strictly required by PHP, it is generally a good practice to add $row = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
523
524
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
525
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
526
527
			if ($row['total'] > 0 && !in_array($action, array('profile', 'logout')) || ($action == 'profile' && $area != 'tfasetup'))
528
				redirectexit('action=profile;area=tfasetup;forced');
529
		}
530
	}
531
532
	// Found 'im, let's set up the variables.
533
	if ($id_member != 0)
534
	{
535
		// Let's not update the last visit time in these cases...
536
		// 1. SSI doesn't count as visiting the forum.
537
		// 2. RSS feeds and XMLHTTP requests don't count either.
538
		// 3. If it was set within this session, no need to set it again.
539
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
540
		// 5. We're still logging in or authenticating
541
		if (SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || !in_array($_REQUEST['action'], array('.xml', 'login2', 'logintfa'))) && empty($_SESSION['id_msg_last_visit']) && (empty($modSettings['cache_enable']) || ($_SESSION['id_msg_last_visit'] = cache_get_data('user_last_visit-' . $id_member, 5 * 3600)) === null))
542
		{
543
			// @todo can this be cached?
544
			// Do a quick query to make sure this isn't a mistake.
545
			$result = $smcFunc['db_query']('', '
546
				SELECT poster_time
547
				FROM {db_prefix}messages
548
				WHERE id_msg = {int:id_msg}
549
				LIMIT 1',
550
				array(
551
					'id_msg' => $user_settings['id_msg_last_visit'],
552
				)
553
			);
554
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
555
			$smcFunc['db_free_result']($result);
556
557
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
558
559
			// If it was *at least* five hours ago...
560
			if ($visitTime < time() - 5 * 3600)
561
			{
562
				updateMemberData($id_member, array('id_msg_last_visit' => (int) $modSettings['maxMsgID'], 'last_login' => time(), 'member_ip' => $_SERVER['REMOTE_ADDR'], 'member_ip2' => $_SERVER['BAN_CHECK_IP']));
563
				$user_settings['last_login'] = time();
564
565
				if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
566
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
567
568
				if (!empty($modSettings['cache_enable']))
569
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
570
			}
571
		}
572
		elseif (empty($_SESSION['id_msg_last_visit']))
573
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
574
575
		$username = $user_settings['member_name'];
576
577
		if (empty($user_settings['additional_groups']))
578
			$user_info = array(
579
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
580
			);
581 View Code Duplication
		else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
582
			$user_info = array(
583
				'groups' => array_merge(
584
					array($user_settings['id_group'], $user_settings['id_post_group']),
585
					explode(',', $user_settings['additional_groups'])
586
				)
587
			);
588
589
		// Because history has proven that it is possible for groups to go bad - clean up in case.
590
		foreach ($user_info['groups'] as $k => $v)
591
			$user_info['groups'][$k] = (int) $v;
592
593
		// This is a logged in user, so definitely not a spider.
594
		$user_info['possibly_robot'] = false;
595
596
		// Figure out the new time offset.
597
		if (!empty($user_settings['timezone']))
598
		{
599
			// Get the offsets from UTC for the server, then for the user.
600
			$tz_system = new DateTimeZone(@date_default_timezone_get());
601
			$tz_user = new DateTimeZone($user_settings['timezone']);
602
			$time_system = new DateTime('now', $tz_system);
603
			$time_user = new DateTime('now', $tz_user);
604
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
605
		}
606
		else
607
		{
608
			// !!! Compatibility.
609
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 :$user_settings['time_offset'];
610
		}
611
	}
612
	// If the user is a guest, initialize all the critical user settings.
613
	else
614
	{
615
		// This is what a guest's variables should be.
616
		$username = '';
617
		$user_info = array('groups' => array(-1));
618
		$user_settings = array();
619
620
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
621
			$_COOKIE[$cookiename] = '';
622
623
		// Expire the 2FA cookie
624
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
625
		{
626
			$tfa_data = smf_json_decode($_COOKIE[$cookiename . '_tfa'], true);
627
628
			if (is_null($tfa_data))
629
				$tfa_data = safe_unserialize($_COOKIE[$cookiename . '_tfa']);
630
631
			list ($id, $user, $exp, $state, $preserve) = $tfa_data;
0 ignored issues
show
Unused Code introduced by
The assignment to $id is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $user is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $state is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
632
633
			if (!$preserve || time() > $exp)
634
			{
635
				$_COOKIE[$cookiename . '_tfa'] = '';
636
				setTFACookie(-3600, 0, '');
637
			}
638
		}
639
640
		// Create a login token if it doesn't exist yet.
641
		if (!isset($_SESSION['token']['post-login']))
642
			createToken('login');
643
		else
644
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
645
646
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
647
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
648
		{
649
			require_once($sourcedir . '/ManageSearchEngines.php');
650
			$user_info['possibly_robot'] = SpiderCheck();
651
		}
652
		elseif (!empty($modSettings['spider_mode']))
653
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
654
		// If we haven't turned on proper spider hunts then have a guess!
655
		else
656
		{
657
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
658
			$user_info['possibly_robot'] = (strpos($_SERVER['HTTP_USER_AGENT'], 'Mozilla') === false && strpos($_SERVER['HTTP_USER_AGENT'], 'Opera') === false) || strpos($ci_user_agent, 'googlebot') !== false || strpos($ci_user_agent, 'slurp') !== false || strpos($ci_user_agent, 'crawl') !== false || strpos($ci_user_agent, 'msnbot') !== false;
659
		}
660
661
		// We don't know the offset...
662
		$user_info['time_offset'] = 0;
663
	}
664
665
	// Set up the $user_info array.
666
	$user_info += array(
667
		'id' => $id_member,
668
		'username' => $username,
669
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
670
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
671
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
672
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
673
		'is_guest' => $id_member == 0,
674
		'is_admin' => in_array(1, $user_info['groups']),
675
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
676
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
677
		'ip' => $_SERVER['REMOTE_ADDR'],
678
		'ip2' => $_SERVER['BAN_CHECK_IP'],
679
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
680
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
681
		'avatar' => array(
682
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
683
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
684
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
685
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
686
		),
687
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
688
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
689
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
690
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
691
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
692
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
693
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
694
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
695
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
696
		'permissions' => array(),
697
	);
698
	$user_info['groups'] = array_unique($user_info['groups']);
699
700
	// Make sure that the last item in the ignore boards array is valid. If the list was too long it could have an ending comma that could cause problems.
701
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
702
		unset($user_info['ignoreboards'][$tmp]);
703
704
	// Allow the user to change their language.
705
	if (!empty($modSettings['userLanguage']))
706
	{
707
		$languages = getLanguages();
708
709
		// Is it valid?
710
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
711
		{
712
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
713
714
			// Make it permanent for members.
715
			if (!empty($user_info['id']))
716
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
717
			else
718
				$_SESSION['language'] = $user_info['language'];
719
		}
720
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
721
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
722
	}
723
724
	// Just build this here, it makes it easier to change/use - administrators can see all boards.
725
	if ($user_info['is_admin'])
726
		$user_info['query_see_board'] = '1=1';
727
	// Otherwise just the groups in $user_info['groups'].
728
	else
729
		$user_info['query_see_board'] = '((FIND_IN_SET(' . implode(', b.member_groups) != 0 OR FIND_IN_SET(', $user_info['groups']) . ', b.member_groups) != 0)' . (!empty($modSettings['deny_boards_access']) ? ' AND (FIND_IN_SET(' . implode(', b.deny_member_groups) = 0 AND FIND_IN_SET(', $user_info['groups']) . ', b.deny_member_groups) = 0)' : '') . (isset($user_info['mod_cache']) ? ' OR ' . $user_info['mod_cache']['mq'] : '') . ')';
730
731
	// Build the list of boards they WANT to see.
732
	// This will take the place of query_see_boards in certain spots, so it better include the boards they can see also
733
734
	// If they aren't ignoring any boards then they want to see all the boards they can see
735
	if (empty($user_info['ignoreboards']))
736
		$user_info['query_wanna_see_board'] = $user_info['query_see_board'];
737
	// Ok I guess they don't want to see all the boards
738
	else
739
		$user_info['query_wanna_see_board'] = '(' . $user_info['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $user_info['ignoreboards']) . '))';
740
741
	call_integration_hook('integrate_user_info');
742
}
743
744
/**
745
 * Check for moderators and see if they have access to the board.
746
 * What it does:
747
 * - sets up the $board_info array for current board information.
748
 * - if cache is enabled, the $board_info array is stored in cache.
749
 * - redirects to appropriate post if only message id is requested.
750
 * - is only used when inside a topic or board.
751
 * - determines the local moderators for the board.
752
 * - adds group id 3 if the user is a local moderator for the board they are in.
753
 * - prevents access if user is not in proper group nor a local moderator of the board.
754
 */
755
function loadBoard()
756
{
757
	global $txt, $scripturl, $context, $modSettings;
758
	global $board_info, $board, $topic, $user_info, $smcFunc;
759
760
	// Assume they are not a moderator.
761
	$user_info['is_mod'] = false;
762
	$context['user']['is_mod'] = &$user_info['is_mod'];
763
764
	// Start the linktree off empty..
765
	$context['linktree'] = array();
766
767
	// Have they by chance specified a message id but nothing else?
768
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
769
	{
770
		// Make sure the message id is really an int.
771
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
772
773
		// Looking through the message table can be slow, so try using the cache first.
774
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
775
		{
776
			$request = $smcFunc['db_query']('', '
777
				SELECT id_topic
778
				FROM {db_prefix}messages
779
				WHERE id_msg = {int:id_msg}
780
				LIMIT 1',
781
				array(
782
					'id_msg' => $_REQUEST['msg'],
783
				)
784
			);
785
786
			// So did it find anything?
787
			if ($smcFunc['db_num_rows']($request))
788
			{
789
				list ($topic) = $smcFunc['db_fetch_row']($request);
790
				$smcFunc['db_free_result']($request);
791
				// Save save save.
792
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
793
			}
794
		}
795
796
		// Remember redirection is the key to avoiding fallout from your bosses.
797
		if (!empty($topic))
798
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
799
		else
800
		{
801
			loadPermissions();
802
			loadTheme();
803
			fatal_lang_error('topic_gone', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
804
		}
805
	}
806
807
	// Load this board only if it is specified.
808
	if (empty($board) && empty($topic))
809
	{
810
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
811
		return;
812
	}
813
814
	if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3))
815
	{
816
		// @todo SLOW?
817
		if (!empty($topic))
818
			$temp = cache_get_data('topic_board-' . $topic, 120);
819
		else
820
			$temp = cache_get_data('board-' . $board, 120);
821
822
		if (!empty($temp))
823
		{
824
			$board_info = $temp;
825
			$board = $board_info['id'];
826
		}
827
	}
828
829
	if (empty($temp))
830
	{
831
		$request = $smcFunc['db_query']('', '
832
			SELECT
833
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
834
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
835
				COALESCE(mem.id_member, 0) AS id_moderator,
836
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
837
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
838
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
839
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
840
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
841
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
842
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
843
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
844
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
845
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
846
			WHERE b.id_board = {raw:board_link}',
847
			array(
848
				'current_topic' => $topic,
849
				'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
850
			)
851
		);
852
		// If there aren't any, skip.
853
		if ($smcFunc['db_num_rows']($request) > 0)
854
		{
855
			$row = $smcFunc['db_fetch_assoc']($request);
856
857
			// Set the current board.
858
			if (!empty($row['id_board']))
859
				$board = $row['id_board'];
860
861
			// Basic operating information. (globals... :/)
862
			$board_info = array(
863
				'id' => $board,
864
				'moderators' => array(),
865
				'moderator_groups' => array(),
866
				'cat' => array(
867
					'id' => $row['id_cat'],
868
					'name' => $row['cname']
869
				),
870
				'name' => $row['bname'],
871
				'description' => $row['description'],
872
				'num_topics' => $row['num_topics'],
873
				'unapproved_topics' => $row['unapproved_topics'],
874
				'unapproved_posts' => $row['unapproved_posts'],
875
				'unapproved_user_topics' => 0,
876
				'parent_boards' => getBoardParents($row['id_parent']),
877
				'parent' => $row['id_parent'],
878
				'child_level' => $row['child_level'],
879
				'theme' => $row['id_theme'],
880
				'override_theme' => !empty($row['override_theme']),
881
				'profile' => $row['id_profile'],
882
				'redirect' => $row['redirect'],
883
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
884
				'posts_count' => empty($row['count_posts']),
885
				'cur_topic_approved' => empty($topic) || $row['approved'],
886
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
887
			);
888
889
			// Load the membergroups allowed, and check permissions.
890
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
891
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
892
893
			do
894
			{
895 View Code Duplication
				if (!empty($row['id_moderator']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
896
					$board_info['moderators'][$row['id_moderator']] = array(
897
						'id' => $row['id_moderator'],
898
						'name' => $row['real_name'],
899
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
900
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
901
					);
902
903 View Code Duplication
				if (!empty($row['id_moderator_group']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
904
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
905
						'id' => $row['id_moderator_group'],
906
						'name' => $row['group_name'],
907
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
908
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
909
					);
910
			}
911
			while ($row = $smcFunc['db_fetch_assoc']($request));
912
913
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
914
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
915
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
916
			{
917
				// Free the previous result
918
				$smcFunc['db_free_result']($request);
919
920
				// @todo why is this using id_topic?
921
				// @todo Can this get cached?
922
				$request = $smcFunc['db_query']('', '
923
					SELECT COUNT(id_topic)
924
					FROM {db_prefix}topics
925
					WHERE id_member_started={int:id_member}
926
						AND approved = {int:unapproved}
927
						AND id_board = {int:board}',
928
					array(
929
						'id_member' => $user_info['id'],
930
						'unapproved' => 0,
931
						'board' => $board,
932
					)
933
				);
934
935
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
936
			}
937
938
			if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3))
939
			{
940
				// @todo SLOW?
941
				if (!empty($topic))
942
					cache_put_data('topic_board-' . $topic, $board_info, 120);
943
				cache_put_data('board-' . $board, $board_info, 120);
944
			}
945
		}
946
		else
947
		{
948
			// Otherwise the topic is invalid, there are no moderators, etc.
949
			$board_info = array(
950
				'moderators' => array(),
951
				'moderator_groups' => array(),
952
				'error' => 'exist'
953
			);
954
			$topic = null;
955
			$board = 0;
956
		}
957
		$smcFunc['db_free_result']($request);
958
	}
959
960
	if (!empty($topic))
961
		$_GET['board'] = (int) $board;
962
963
	if (!empty($board))
964
	{
965
		// Get this into an array of keys for array_intersect
966
		$moderator_groups = array_keys($board_info['moderator_groups']);
967
968
		// Now check if the user is a moderator.
969
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
970
971 View Code Duplication
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
972
			$board_info['error'] = 'access';
973 View Code Duplication
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
974
			$board_info['error'] = 'access';
975
976
		// Build up the linktree.
977
		$context['linktree'] = array_merge(
978
			$context['linktree'],
979
			array(array(
980
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
981
				'name' => $board_info['cat']['name']
982
			)),
983
			array_reverse($board_info['parent_boards']),
984
			array(array(
985
				'url' => $scripturl . '?board=' . $board . '.0',
986
				'name' => $board_info['name']
987
			))
988
		);
989
	}
990
991
	// Set the template contextual information.
992
	$context['user']['is_mod'] = &$user_info['is_mod'];
993
	$context['current_topic'] = $topic;
994
	$context['current_board'] = $board;
995
996
	// No posting in redirection boards!
997
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
998
		$board_info['error'] == 'post_in_redirect';
999
1000
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1001
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1002
	{
1003
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1004
		loadPermissions();
1005
		loadTheme();
1006
1007
		$_GET['board'] = '';
1008
		$_GET['topic'] = '';
1009
1010
		// The linktree should not give the game away mate!
1011
		$context['linktree'] = array(
1012
			array(
1013
				'url' => $scripturl,
1014
				'name' => $context['forum_name_html_safe']
1015
			)
1016
		);
1017
1018
		// If it's a prefetching agent or we're requesting an attachment.
1019
		if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach'))
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $_REQUEST['action'] (integer) and 'dlattach' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1020
		{
1021
			ob_end_clean();
1022
			header('HTTP/1.1 403 Forbidden');
1023
			die;
1024
		}
1025
		elseif ($board_info['error'] == 'post_in_redirect')
1026
		{
1027
			// Slightly different error message here...
1028
			fatal_lang_error('cannot_post_redirect', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1029
		}
1030
		elseif ($user_info['is_guest'])
1031
		{
1032
			loadLanguage('Errors');
1033
			is_not_guest($txt['topic_gone']);
1034
		}
1035
		else
1036
			fatal_lang_error('topic_gone', false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1037
	}
1038
1039
	if ($user_info['is_mod'])
1040
		$user_info['groups'][] = 3;
1041
}
1042
1043
/**
1044
 * Load this user's permissions.
1045
 */
1046
function loadPermissions()
1047
{
1048
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir;
1049
1050
	if ($user_info['is_admin'])
1051
	{
1052
		banPermissions();
1053
		return;
1054
	}
1055
1056
	if (!empty($modSettings['cache_enable']))
1057
	{
1058
		$cache_groups = $user_info['groups'];
1059
		asort($cache_groups);
1060
		$cache_groups = implode(',', $cache_groups);
1061
		// If it's a spider then cache it different.
1062
		if ($user_info['possibly_robot'])
1063
			$cache_groups .= '-spider';
1064
1065
		if ($modSettings['cache_enable'] >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1066
		{
1067
			list ($user_info['permissions']) = $temp;
1068
			banPermissions();
1069
1070
			return;
1071
		}
1072
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1073
			list ($user_info['permissions'], $removals) = $temp;
1074
	}
1075
1076
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1077
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1078
1079
	if (empty($user_info['permissions']))
1080
	{
1081
		// Get the general permissions.
1082
		$request = $smcFunc['db_query']('', '
1083
			SELECT permission, add_deny
1084
			FROM {db_prefix}permissions
1085
			WHERE id_group IN ({array_int:member_groups})
1086
				' . $spider_restrict,
1087
			array(
1088
				'member_groups' => $user_info['groups'],
1089
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1090
			)
1091
		);
1092
		$removals = array();
1093 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1094
		{
1095
			if (empty($row['add_deny']))
1096
				$removals[] = $row['permission'];
1097
			else
1098
				$user_info['permissions'][] = $row['permission'];
1099
		}
1100
		$smcFunc['db_free_result']($request);
1101
1102
		if (isset($cache_groups))
1103
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1104
	}
1105
1106
	// Get the board permissions.
1107
	if (!empty($board))
1108
	{
1109
		// Make sure the board (if any) has been loaded by loadBoard().
1110
		if (!isset($board_info['profile']))
1111
			fatal_lang_error('no_board');
1112
1113
		$request = $smcFunc['db_query']('', '
1114
			SELECT permission, add_deny
1115
			FROM {db_prefix}board_permissions
1116
			WHERE (id_group IN ({array_int:member_groups})
1117
				' . $spider_restrict . ')
1118
				AND id_profile = {int:id_profile}',
1119
			array(
1120
				'member_groups' => $user_info['groups'],
1121
				'id_profile' => $board_info['profile'],
1122
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1123
			)
1124
		);
1125 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1126
		{
1127
			if (empty($row['add_deny']))
1128
				$removals[] = $row['permission'];
0 ignored issues
show
Bug introduced by
The variable $removals 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...
1129
			else
1130
				$user_info['permissions'][] = $row['permission'];
1131
		}
1132
		$smcFunc['db_free_result']($request);
1133
	}
1134
1135
	// Remove all the permissions they shouldn't have ;).
1136
	if (!empty($modSettings['permission_enable_deny']))
1137
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
1138
1139
	if (isset($cache_groups) && !empty($board) && $modSettings['cache_enable'] >= 2)
1140
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1141
1142
	// Banned?  Watch, don't touch..
1143
	banPermissions();
1144
1145
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1146
	if (!$user_info['is_guest'])
1147
	{
1148
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1149
		{
1150
			require_once($sourcedir . '/Subs-Auth.php');
1151
			rebuildModCache();
1152
		}
1153
		else
1154
			$user_info['mod_cache'] = $_SESSION['mc'];
1155
1156
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1157
		// For example this drastically simplifies certain changes to the profile area.
1158
		$user_info['permissions'][] = 'is_not_guest';
1159
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1160
		$user_info['permissions'][] = 'profile_view_own';
1161
		if (in_array('profile_view', $user_info['permissions']))
1162
			$user_info['permissions'][] = 'profile_view_any';
1163
	}
1164
}
1165
1166
/**
1167
 * Loads an array of users' data by ID or member_name.
1168
 *
1169
 * @param array|string $users An array of users by id or name or a single username/id
1170
 * @param bool $is_name Whether $users contains names
1171
 * @param string $set What kind of data to load (normal, profile, minimal)
1172
 * @return array The ids of the members loaded
1173
 */
1174
function loadMemberData($users, $is_name = false, $set = 'normal')
1175
{
1176
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1177
	global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1178
1179
	// Can't just look for no users :P.
1180
	if (empty($users))
1181
		return array();
1182
1183
	// Pass the set value
1184
	$context['loadMemberContext_set'] = $set;
1185
1186
	// Make sure it's an array.
1187
	$users = !is_array($users) ? array($users) : array_unique($users);
1188
	$loaded_ids = array();
1189
1190
	if (!$is_name && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1191
	{
1192
		$users = array_values($users);
1193
		for ($i = 0, $n = count($users); $i < $n; $i++)
1194
		{
1195
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1196
			if ($data == null)
1197
				continue;
1198
1199
			$loaded_ids[] = $data['id_member'];
1200
			$user_profile[$data['id_member']] = $data;
1201
			unset($users[$i]);
1202
		}
1203
	}
1204
1205
	// Used by default
1206
	$select_columns = '
1207
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1208
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1209
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1210
			mem.birthdate, mem.member_ip, mem.member_ip2, mem.posts, mem.last_login, mem.id_post_group, mem.lngfile, mem.id_group, mem.time_offset, mem.show_online,
1211
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1212
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1213
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1214
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1215
	$select_tables = '
1216
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1217
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1218
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1219
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1220
1221
	// We add or replace according the the set
1222
	switch ($set)
1223
	{
1224
		case 'normal':
1225
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1226
			break;
1227
		case 'profile':
1228
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1229
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1230
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1231
			break;
1232
		case 'minimal':
1233
			$select_columns = '
1234
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1235
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1236
			$select_tables = '';
1237
			break;
1238
		default:
1239
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1240
	}
1241
1242
	// Allow mods to easily add to the selected member data
1243
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1244
1245
	if (!empty($users))
1246
	{
1247
		// Load the member's data.
1248
		$request = $smcFunc['db_query']('', '
1249
			SELECT' . $select_columns . '
1250
			FROM {db_prefix}members AS mem' . $select_tables . '
1251
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1252
			array(
1253
				'blank_string' => '',
1254
				'users' => $users,
1255
			)
1256
		);
1257
		$new_loaded_ids = array();
1258
		while ($row = $smcFunc['db_fetch_assoc']($request))
1259
		{
1260
			// Take care of proxying avatar if required, do this here for maximum reach
1261 View Code Duplication
			if ($image_proxy_enabled && !empty($row['avatar']) && stripos($row['avatar'], 'http://') !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1262
				$row['avatar'] = $boardurl . '/proxy.php?request=' . urlencode($row['avatar']) . '&hash=' . md5($row['avatar'] . $image_proxy_secret);
1263
1264
			if ( isset($row['member_ip']) )
1265
				$row['member_ip'] = inet_dtop($row['member_ip']);
1266
			if ( isset($row['member_ip2']) )
1267
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1268
			$new_loaded_ids[] = $row['id_member'];
1269
			$loaded_ids[] = $row['id_member'];
1270
			$row['options'] = array();
1271
			$user_profile[$row['id_member']] = $row;
1272
		}
1273
		$smcFunc['db_free_result']($request);
1274
	}
1275
1276 View Code Duplication
	if (!empty($new_loaded_ids) && $set !== 'minimal')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1277
	{
1278
		$request = $smcFunc['db_query']('', '
1279
			SELECT *
1280
			FROM {db_prefix}themes
1281
			WHERE id_member IN ({array_int:loaded_ids})',
1282
			array(
1283
				'loaded_ids' => $new_loaded_ids,
1284
			)
1285
		);
1286
		while ($row = $smcFunc['db_fetch_assoc']($request))
1287
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1288
		$smcFunc['db_free_result']($request);
1289
	}
1290
1291
	$additional_mods = array();
1292
1293
	// Are any of these users in groups assigned to moderate this board?
1294
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1295
	{
1296
		foreach ($loaded_ids as $a_member)
1297
		{
1298
			if (!empty($user_profile[$a_member]['additional_groups']))
1299
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1300
			else
1301
				$groups = array($user_profile[$a_member]['id_group']);
1302
1303
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1304
1305
			if (!empty($temp))
1306
			{
1307
				$additional_mods[] = $a_member;
1308
			}
1309
		}
1310
	}
1311
1312
	if (!empty($new_loaded_ids) && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1313
	{
1314
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1315
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1316
	}
1317
1318
	// Are we loading any moderators?  If so, fix their group data...
1319
	if (!empty($loaded_ids) && (!empty($board_info['moderators']) || !empty($board_info['moderator_groups'])) && $set === 'normal' && count($temp_mods = array_merge(array_intersect($loaded_ids, array_keys($board_info['moderators'])), $additional_mods)) !== 0)
1320
	{
1321 View Code Duplication
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1322
		{
1323
			$request = $smcFunc['db_query']('', '
1324
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1325
				FROM {db_prefix}membergroups
1326
				WHERE id_group = {int:moderator_group}
1327
				LIMIT 1',
1328
				array(
1329
					'moderator_group' => 3,
1330
				)
1331
			);
1332
			$row = $smcFunc['db_fetch_assoc']($request);
1333
			$smcFunc['db_free_result']($request);
1334
1335
			cache_put_data('moderator_group_info', $row, 480);
1336
		}
1337
1338
		foreach ($temp_mods as $id)
1339
		{
1340
			// By popular demand, don't show admins or global moderators as moderators.
1341
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1342
				$user_profile[$id]['member_group'] = $row['member_group'];
1343
1344
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1345
			if (!empty($row['icons']))
1346
				$user_profile[$id]['icons'] = $row['icons'];
1347
			if (!empty($row['member_group_color']))
1348
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1349
		}
1350
	}
1351
1352
	return $loaded_ids;
1353
}
1354
1355
/**
1356
 * Loads the user's basic values... meant for template/theme usage.
1357
 *
1358
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1359
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1360
 * @return boolean Whether or not the data was loaded successfully
1361
 */
1362
function loadMemberContext($user, $display_custom_fields = false)
1363
{
1364
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1365
	global $context, $modSettings, $settings, $smcFunc;
1366
	static $dataLoaded = array();
1367
	static $loadedLanguages = array();
1368
1369
	// If this person's data is already loaded, skip it.
1370
	if (isset($dataLoaded[$user]))
1371
		return true;
1372
1373
	// We can't load guests or members not loaded by loadMemberData()!
1374
	if ($user == 0)
1375
		return false;
1376
	if (!isset($user_profile[$user]))
1377
	{
1378
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1379
		return false;
1380
	}
1381
1382
	// Well, it's loaded now anyhow.
1383
	$dataLoaded[$user] = true;
1384
	$profile = $user_profile[$user];
1385
1386
	// Censor everything.
1387
	censorText($profile['signature']);
1388
	censorText($profile['personal_text']);
1389
1390
	// Set things up to be used before hand.
1391
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1392
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1393
1394
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1395
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1396
	// Setup the buddy status here (One whole in_array call saved :P)
1397
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1398
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1399
1400
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1401
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1402
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1403
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1404
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1405
	else //not set, bye bye
1406
		$group_icon_url = '';
1407
1408
	// These minimal values are always loaded
1409
	$memberContext[$user] = array(
1410
		'username' => $profile['member_name'],
1411
		'name' => $profile['real_name'],
1412
		'id' => $profile['id_member'],
1413
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1414
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . $profile['real_name'] . '" '. (!empty($modSettings['onlineEnable']) ? 'class="pm_icon"' : '').'>' . $profile['real_name'] . '</a>',
1415
		'email' => $profile['email_address'],
1416
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1417
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1418
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1419
	);
1420
1421
	// If the set isn't minimal then load the monstrous array.
1422
	if ($context['loadMemberContext_set'] != 'minimal')
1423
	{
1424
		// Go the extra mile and load the user's native language name.
1425
		if (empty($loadedLanguages))
1426
			$loadedLanguages = getLanguages();
1427
1428
		$memberContext[$user] += array(
1429
			'username_color' => '<span '. (!empty($profile['member_group_color']) ? 'style="color:'. $profile['member_group_color'] .';"' : '') .'>'. $profile['member_name'] .'</span>',
1430
			'name_color' => '<span '. (!empty($profile['member_group_color']) ? 'style="color:'. $profile['member_group_color'] .';"' : '') .'>'. $profile['real_name'] .'</span>',
1431
			'link_color' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . $profile['real_name'] . '" '. (!empty($profile['member_group_color']) ? 'style="color:'. $profile['member_group_color'] .';"' : '') .'>' . $profile['real_name'] . '</a>',
1432
			'is_buddy' => $profile['buddy'],
1433
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1434
			'buddies' => $buddy_list,
1435
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1436
			'blurb' => $profile['personal_text'],
1437
			'website' => array(
1438
				'title' => $profile['website_title'],
1439
				'url' => $profile['website_url'],
1440
			),
1441
			'birth_date' => empty($profile['birthdate']) || $profile['birthdate'] === '0001-01-01' ? '0000-00-00' : (substr($profile['birthdate'], 0, 4) === '0004' ? '0000' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1442
			'signature' => $profile['signature'],
1443
			'real_posts' => $profile['posts'],
1444
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1445
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1446
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
0 ignored issues
show
Documentation introduced by
0 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1447
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1448
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1449
			'online' => array(
1450
				'is_online' => $profile['is_online'],
1451
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1452
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1453
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1454
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1455
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1456
			),
1457
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1458
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1459
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1460
			'options' => $profile['options'],
1461
			'is_guest' => false,
1462
			'group' => $profile['member_group'],
1463
			'group_color' => $profile['member_group_color'],
1464
			'group_id' => $profile['id_group'],
1465
			'post_group' => $profile['post_group'],
1466
			'post_group_color' => $profile['post_group_color'],
1467
			'group_icons' => str_repeat('<img src="' . str_replace('$language', $context['user']['language'], isset($profile['icons'][1]) ? $group_icon_url : '') . '" alt="*">', empty($profile['icons'][0]) || empty($profile['icons'][1]) ? 0 : $profile['icons'][0]),
1468
			'warning' => $profile['warning'],
1469
			'warning_status' => !empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $profile['warning'] ? 'mute' : (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $profile['warning'] ? 'moderate' : (!empty($modSettings['warning_watch']) && $modSettings['warning_watch'] <= $profile['warning'] ? 'watch' : (''))),
1470
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1471
			'custom_fields' => array(),
1472
		);
1473
	}
1474
1475
	// If the set isn't minimal then load their avatar as well.
1476
	if ($context['loadMemberContext_set'] != 'minimal')
1477
	{
1478
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1479
		{
1480
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1481
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1482
			else
1483
				$image = get_gravatar_url($profile['email_address']);
1484
		}
1485
		else
1486
		{
1487
			// So it's stored in the member table?
1488
			if (!empty($profile['avatar']))
1489
			{
1490
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1491
			}
1492
			elseif (!empty($profile['filename']))
1493
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1494
			// Right... no avatar...use the default one
1495
			else
1496
				$image = $modSettings['avatar_url'] . '/default.png';
1497
		}
1498
		if (!empty($image))
1499
			$memberContext[$user]['avatar'] = array(
1500
				'name' => $profile['avatar'],
1501
				'image' => '<img class="avatar" src="' . $image . '" alt="avatar_'. $profile['member_name'].'">',
1502
				'href' => $image,
1503
				'url' => $image,
1504
			);
1505
	}
1506
1507
	// Are we also loading the members custom fields into context?
1508
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1509
	{
1510
		$memberContext[$user]['custom_fields'] = array();
1511
1512
		if (!isset($context['display_fields']))
1513
			$context['display_fields'] = smf_json_decode($modSettings['displayFields'], true);
1514
1515
		foreach ($context['display_fields'] as $custom)
1516
		{
1517
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1518
				continue;
1519
1520
			$value = $profile['options'][$custom['col_name']];
1521
1522
			// Don't show the "disabled" option for the "gender" field.
1523
			if ($custom['col_name'] == 'cust_gender' && $value == 'Disabled')
1524
				continue;
1525
1526
			// BBC?
1527
			if ($custom['bbc'])
1528
				$value = parse_bbc($value);
1529
			// ... or checkbox?
1530
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1531
				$value = $value ? $txt['yes'] : $txt['no'];
1532
1533
			// Enclosing the user input within some other text?
1534 View Code Duplication
			if (!empty($custom['enclose']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1535
				$value = strtr($custom['enclose'], array(
1536
					'{SCRIPTURL}' => $scripturl,
1537
					'{IMAGES_URL}' => $settings['images_url'],
1538
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1539
					'{INPUT}' => $value,
1540
				));
1541
1542
			$memberContext[$user]['custom_fields'][] = array(
1543
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1544
				'col_name' => $custom['col_name'],
1545
				'value' => un_htmlspecialchars($value),
1546
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1547
			);
1548
		}
1549
	}
1550
1551
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1552
	return true;
1553
}
1554
1555
/**
1556
 * Loads the user's custom profile fields
1557
 *
1558
 * @param integer|array $users A single user ID or an array of user IDs
1559
 * @param string|array $params Either a string or an array of strings with profile field names
1560
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1561
 */
1562
function loadMemberCustomFields($users, $params)
1563
{
1564
	global $smcFunc, $txt, $scripturl, $settings;
1565
1566
	// Do not waste my time...
1567
	if (empty($users) || empty($params))
1568
		return false;
1569
1570
	// Make sure it's an array.
1571
	$users = !is_array($users) ? array($users) : array_unique($users);
1572
	$params = !is_array($params) ? array($params) : array_unique($params);
1573
	$return = array();
1574
1575
	$request = $smcFunc['db_query']('', '
1576
		SELECT c.id_field, c.col_name, c.field_name, c.field_desc, c.field_type, c.field_order, c.field_length, c.field_options, c.mask, show_reg,
1577
		c.show_display, c.show_profile, c.private, c.active, c.bbc, c.can_search, c.default_value, c.enclose, c.placement, t.variable, t.value, t.id_member
1578
		FROM {db_prefix}themes AS t
1579
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1580
		WHERE id_member IN ({array_int:loaded_ids})
1581
			AND variable IN ({array_string:params})
1582
		ORDER BY field_order',
1583
		array(
1584
			'loaded_ids' => $users,
1585
			'params' => $params,
1586
		)
1587
	);
1588
1589
	while ($row = $smcFunc['db_fetch_assoc']($request))
1590
	{
1591
		// BBC?
1592
		if (!empty($row['bbc']))
1593
			$row['value'] = parse_bbc($row['value']);
1594
1595
		// ... or checkbox?
1596
		elseif (isset($row['type']) && $row['type'] == 'check')
1597
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1598
1599
		// Enclosing the user input within some other text?
1600
		if (!empty($row['enclose']))
1601
			$row['value'] = strtr($row['enclose'], array(
1602
				'{SCRIPTURL}' => $scripturl,
1603
				'{IMAGES_URL}' => $settings['images_url'],
1604
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1605
				'{INPUT}' => un_htmlspecialchars($row['value']),
1606
			));
1607
1608
		// Send a simple array if there is just 1 param
1609
		if (count($params) == 1)
1610
			$return[$row['id_member']] = $row;
1611
1612
		// More than 1? knock yourself out...
1613
		else
1614
		{
1615
			if (!isset($return[$row['id_member']]))
1616
				$return[$row['id_member']] = array();
1617
1618
			$return[$row['id_member']][$row['variable']] = $row;
1619
		}
1620
	}
1621
1622
	$smcFunc['db_free_result']($request);
1623
1624
	return !empty($return) ? $return : false;
1625
}
1626
1627
/**
1628
 * Loads information about what browser the user is viewing with and places it in $context
1629
 *  - uses the class from {@link Class-BrowserDetect.php}
1630
 */
1631
function detectBrowser()
1632
{
1633
	// Load the current user's browser of choice
1634
	$detector = new browser_detector;
1635
	$detector->detectBrowser();
1636
}
1637
1638
/**
1639
 * Are we using this browser?
1640
 *
1641
 * Wrapper function for detectBrowser
1642
 * @param string $browser The browser we are checking for.
1643
 * @return bool Whether or not the current browser is what we're looking for
1644
*/
1645
function isBrowser($browser)
1646
{
1647
	global $context;
1648
1649
	// Don't know any browser!
1650
	if (empty($context['browser']))
1651
		detectBrowser();
1652
1653
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1654
}
1655
1656
/**
1657
 * Load a theme, by ID.
1658
 *
1659
 * @param int $id_theme The ID of the theme to load
1660
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1661
 */
1662
function loadTheme($id_theme = 0, $initialize = true)
1663
{
1664
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1665
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1666
	global $context, $settings, $options, $sourcedir, $ssi_theme, $smcFunc, $language, $board, $image_proxy_enabled;
1667
1668
	// The theme was specified by parameter.
1669
	if (!empty($id_theme))
1670
		$id_theme = (int) $id_theme;
1671
	// The theme was specified by REQUEST.
1672 View Code Duplication
	elseif (!empty($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1673
	{
1674
		$id_theme = (int) $_REQUEST['theme'];
1675
		$_SESSION['id_theme'] = $id_theme;
1676
	}
1677
	// The theme was specified by REQUEST... previously.
1678 View Code Duplication
	elseif (!empty($_SESSION['id_theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1679
		$id_theme = (int) $_SESSION['id_theme'];
1680
	// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1681
	elseif (!empty($user_info['theme']) && !isset($_REQUEST['theme']))
1682
		$id_theme = $user_info['theme'];
1683
	// The theme was specified by the board.
1684
	elseif (!empty($board_info['theme']))
1685
		$id_theme = $board_info['theme'];
1686
	// The theme is the forum's default.
1687
	else
1688
		$id_theme = $modSettings['theme_guests'];
1689
1690
	// Verify the id_theme... no foul play.
1691
	// Always allow the board specific theme, if they are overriding.
1692
	if (!empty($board_info['theme']) && $board_info['override_theme'])
1693
		$id_theme = $board_info['theme'];
1694
	// If they have specified a particular theme to use with SSI allow it to be used.
1695
	elseif (!empty($ssi_theme) && $id_theme == $ssi_theme)
1696
		$id_theme = (int) $id_theme;
1697
	elseif (!empty($modSettings['enableThemes']) && !allowedTo('admin_forum'))
1698
	{
1699
		$themes = explode(',', $modSettings['enableThemes']);
1700
		if (!in_array($id_theme, $themes))
1701
			$id_theme = $modSettings['theme_guests'];
1702
		else
1703
			$id_theme = (int) $id_theme;
1704
	}
1705
	else
1706
		$id_theme = (int) $id_theme;
1707
1708
	$member = empty($user_info['id']) ? -1 : $user_info['id'];
1709
1710
	// Disable image proxy if we don't have SSL enabled
1711
	if (empty($modSettings['force_ssl']) || $modSettings['force_ssl'] < 2)
1712
		$image_proxy_enabled = false;
1713
1714
	if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && ($temp = cache_get_data('theme_settings-' . $id_theme . ':' . $member, 60)) != null && time() - 60 > $modSettings['settings_updated'])
1715
	{
1716
		$themeData = $temp;
1717
		$flag = true;
1718
	}
1719
	elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
1720
		$themeData = $temp + array($member => array());
1721
	else
1722
		$themeData = array(-1 => array(), 0 => array(), $member => array());
1723
1724
	if (empty($flag))
1725
	{
1726
		// Load variables from the current or default theme, global or this user's.
1727
		$result = $smcFunc['db_query']('', '
1728
			SELECT variable, value, id_member, id_theme
1729
			FROM {db_prefix}themes
1730
			WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1731
				AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)'),
1732
			array(
1733
				'id_theme' => $id_theme,
1734
				'id_member' => $member,
1735
			)
1736
		);
1737
		// Pick between $settings and $options depending on whose data it is.
1738
		while ($row = $smcFunc['db_fetch_assoc']($result))
1739
		{
1740
			// There are just things we shouldn't be able to change as members.
1741
			if ($row['id_member'] != 0 && in_array($row['variable'], array('actual_theme_url', 'actual_images_url', 'base_theme_dir', 'base_theme_url', 'default_images_url', 'default_theme_dir', 'default_theme_url', 'default_template', 'images_url', 'number_recent_posts', 'smiley_sets_default', 'theme_dir', 'theme_id', 'theme_layers', 'theme_templates', 'theme_url')))
1742
				continue;
1743
1744
			// If this is the theme_dir of the default theme, store it.
1745
			if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1746
				$themeData[0]['default_' . $row['variable']] = $row['value'];
1747
1748
			// If this isn't set yet, is a theme option, or is not the default theme..
1749
			if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1750
				$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1751
		}
1752
		$smcFunc['db_free_result']($result);
1753
1754
		if (!empty($themeData[-1]))
1755
			foreach ($themeData[-1] as $k => $v)
1756
			{
1757
				if (!isset($themeData[$member][$k]))
1758
					$themeData[$member][$k] = $v;
1759
			}
1760
1761
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
1762
			cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1763
		// Only if we didn't already load that part of the cache...
1764
		elseif (!isset($temp))
1765
			cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1766
	}
1767
1768
	$settings = $themeData[0];
1769
	$options = $themeData[$member];
1770
1771
	$settings['theme_id'] = $id_theme;
1772
1773
	$settings['actual_theme_url'] = $settings['theme_url'];
1774
	$settings['actual_images_url'] = $settings['images_url'];
1775
	$settings['actual_theme_dir'] = $settings['theme_dir'];
1776
1777
	$settings['template_dirs'] = array();
1778
	// This theme first.
1779
	$settings['template_dirs'][] = $settings['theme_dir'];
1780
1781
	// Based on theme (if there is one).
1782
	if (!empty($settings['base_theme_dir']))
1783
		$settings['template_dirs'][] = $settings['base_theme_dir'];
1784
1785
	// Lastly the default theme.
1786 View Code Duplication
	if ($settings['theme_dir'] != $settings['default_theme_dir'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1787
		$settings['template_dirs'][] = $settings['default_theme_dir'];
1788
1789
	if (!$initialize)
1790
		return;
1791
1792
	// Check to see if we're forcing SSL
1793
	if (!empty($modSettings['force_ssl']) && $modSettings['force_ssl'] == 2 && empty($maintenance) &&
1794
		(!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off') && SMF != 'SSI')
1795
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')));
1796
1797
	// Check to see if they're accessing it from the wrong place.
1798
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1799
	{
1800
		$detected_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on' ? 'https://' : 'http://';
1801
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1802
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1803
		if ($temp != '/')
1804
			$detected_url .= $temp;
1805
	}
1806
	if (isset($detected_url) && $detected_url != $boardurl)
1807
	{
1808
		// Try #1 - check if it's in a list of alias addresses.
1809
		if (!empty($modSettings['forum_alias_urls']))
1810
		{
1811
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1812
1813
			foreach ($aliases as $alias)
1814
			{
1815
				// Rip off all the boring parts, spaces, etc.
1816
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
1817
					$do_fix = true;
1818
			}
1819
		}
1820
1821
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
1822
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI')
1823
		{
1824
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
1825
			if (empty($_GET))
1826
				redirectexit('wwwRedirect');
1827
			else
1828
			{
1829
				list ($k, $v) = each($_GET);
1830
1831
				if ($k != 'wwwRedirect')
1832
					redirectexit('wwwRedirect;' . $k . '=' . $v);
1833
			}
1834
		}
1835
1836
		// #3 is just a check for SSL...
1837
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
1838
			$do_fix = true;
1839
1840
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
1841
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
1842
		{
1843
			// Caching is good ;).
1844
			$oldurl = $boardurl;
1845
1846
			// Fix $boardurl and $scripturl.
1847
			$boardurl = $detected_url;
1848
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
1849
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
1850
1851
			// Fix the theme urls...
1852
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
1853
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
1854
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
1855
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
1856
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
1857
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
1858
1859
			// And just a few mod settings :).
1860
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
1861
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
1862
1863
			// Clean up after loadBoard().
1864
			if (isset($board_info['moderators']))
1865
			{
1866
				foreach ($board_info['moderators'] as $k => $dummy)
1867
				{
1868
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
1869
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
1870
				}
1871
			}
1872
			foreach ($context['linktree'] as $k => $dummy)
1873
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
1874
		}
1875
	}
1876
	// Set up the contextual user array.
1877
	if (!empty($user_info))
1878
	{
1879
		$context['user'] = array(
1880
			'id' => $user_info['id'],
1881
			'is_logged' => !$user_info['is_guest'],
1882
			'is_guest' => &$user_info['is_guest'],
1883
			'is_admin' => &$user_info['is_admin'],
1884
			'is_mod' => &$user_info['is_mod'],
1885
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
1886
			'can_mod' => allowedTo('access_mod_center') || (!$user_info['is_guest'] && ($user_info['mod_cache']['gq'] != '0=1' || $user_info['mod_cache']['bq'] != '0=1' || ($modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap'])))),
1887
			'username' => $user_info['username'],
1888
			'language' => $user_info['language'],
1889
			'email' => $user_info['email'],
1890
			'ignoreusers' => $user_info['ignoreusers'],
1891
		);
1892
		if (!$context['user']['is_guest'])
1893
			$context['user']['name'] = $user_info['name'];
1894 View Code Duplication
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1895
			$context['user']['name'] = $txt['guest_title'];
1896
1897
		// Determine the current smiley set.
1898
		$user_info['smiley_set'] = (!in_array($user_info['smiley_set'], explode(',', $modSettings['smiley_sets_known'])) && $user_info['smiley_set'] != 'none') || empty($modSettings['smiley_sets_enable']) ? (!empty($settings['smiley_sets_default']) ? $settings['smiley_sets_default'] : $modSettings['smiley_sets_default']) : $user_info['smiley_set'];
1899
		$context['user']['smiley_set'] = $user_info['smiley_set'];
1900
	}
1901
	else
1902
	{
1903
		$context['user'] = array(
1904
			'id' => -1,
1905
			'is_logged' => false,
1906
			'is_guest' => true,
1907
			'is_mod' => false,
1908
			'can_mod' => false,
1909
			'name' => $txt['guest_title'],
1910
			'language' => $language,
1911
			'email' => '',
1912
			'ignoreusers' => array(),
1913
		);
1914
	}
1915
1916
	// Some basic information...
1917
	if (!isset($context['html_headers']))
1918
		$context['html_headers'] = '';
1919
	if (!isset($context['javascript_files']))
1920
		$context['javascript_files'] = array();
1921
	if (!isset($context['css_files']))
1922
		$context['css_files'] = array();
1923
	if (!isset($context['css_header']))
1924
		$context['css_header'] = array();
1925
	if (!isset($context['javascript_inline']))
1926
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
1927
	if (!isset($context['javascript_vars']))
1928
		$context['javascript_vars'] = array();
1929
1930
	$context['login_url'] = (!empty($modSettings['force_ssl']) && $modSettings['force_ssl'] < 2 ? strtr($scripturl, array('http://' => 'https://')) : $scripturl) . '?action=login2';
1931
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
1932
	$context['session_var'] = $_SESSION['session_var'];
1933
	$context['session_id'] = $_SESSION['session_value'];
1934
	$context['forum_name'] = $mbname;
1935
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
1936
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
1937
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
1938
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
1939
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
1940
	if (isset($modSettings['load_average']))
1941
		$context['load_average'] = $modSettings['load_average'];
1942
1943
	// Detect the browser. This is separated out because it's also used in attachment downloads
1944
	detectBrowser();
1945
1946
	// Set the top level linktree up.
1947
	array_unshift($context['linktree'], array(
1948
		'url' => $scripturl,
1949
		'name' => $context['forum_name_html_safe']
1950
	));
1951
1952
	// This allows sticking some HTML on the page output - useful for controls.
1953
	$context['insert_after_template'] = '';
1954
1955
	if (!isset($txt))
1956
		$txt = array();
1957
1958
	$simpleActions = array(
1959
		'findmember',
1960
		'helpadmin',
1961
		'printpage',
1962
		'spellcheck',
1963
	);
1964
1965
	// Parent action => array of areas
1966
	$simpleAreas = array(
1967
		'profile' => array('popup', 'alerts_popup',),
1968
	);
1969
1970
	// Parent action => array of subactions
1971
	$simpleSubActions = array(
1972
		'pm' => array('popup',),
1973
	);
1974
1975
	// Actions that specifically uses XML output.
1976
	$xmlActions = array(
1977
		'quotefast',
1978
		'jsmodify',
1979
		'xmlhttp',
1980
		'post2',
1981
		'suggest',
1982
		'stats',
1983
	);
1984
1985
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$xmlActions));
1986
1987
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
1988
	(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
1989
	(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
1990
1991
	// Output is fully XML, so no need for the index template.
1992
	if (isset($_REQUEST['xml']) && in_array($context['current_action'], $xmlActions))
1993
	{
1994
		loadLanguage('index+Modifications');
1995
		loadTemplate('Xml');
1996
		$context['template_layers'] = array();
1997
	}
1998
1999
	// These actions don't require the index template at all.
2000
	elseif (!empty($context['simple_action']))
2001
	{
2002
		loadLanguage('index+Modifications');
2003
		$context['template_layers'] = array();
2004
	}
2005
2006
	else
2007
	{
2008
		// Custom templates to load, or just default?
2009
		if (isset($settings['theme_templates']))
2010
			$templates = explode(',', $settings['theme_templates']);
2011
		else
2012
			$templates = array('index');
2013
2014
		// Load each template...
2015
		foreach ($templates as $template)
2016
			loadTemplate($template);
2017
2018
		// ...and attempt to load their associated language files.
2019
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2020
		loadLanguage($required_files, '', false);
2021
2022
		// Custom template layers?
2023
		if (isset($settings['theme_layers']))
2024
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2025
		else
2026
			$context['template_layers'] = array('html', 'body');
2027
	}
2028
2029
	// Initialize the theme.
2030
	loadSubTemplate('init', 'ignore');
0 ignored issues
show
Documentation introduced by
'ignore' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2031
2032
	// Allow overriding the board wide time/number formats.
2033
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2034
		$user_info['time_format'] = $txt['time_format'];
2035
2036
	// Set the character set from the template.
2037
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2038
	$context['utf8'] = $context['character_set'] === 'UTF-8';
2039
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2040
2041
	// Guests may still need a name.
2042 View Code Duplication
	if ($context['user']['is_guest'] && empty($context['user']['name']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2043
		$context['user']['name'] = $txt['guest_title'];
2044
2045
	// Any theme-related strings that need to be loaded?
2046
	if (!empty($settings['require_theme_strings']))
2047
		loadLanguage('ThemeStrings', '', false);
2048
2049
	// Make a special URL for the language.
2050
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2051
2052
	// And of course, let's load the default CSS file.
2053
	loadCSSFile('index.css', array('minimize' => true), 'smf_index');
2054
2055
	// Here is my luvly Responsive CSS
2056
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true), 'smf_responsive');
2057
2058
	if ($context['right_to_left'])
2059
		loadCSSFile('rtl.css', array(), 'smf_rtl');
2060
2061
	// We allow theme variants, because we're cool.
2062
	$context['theme_variant'] = '';
2063
	$context['theme_variant_url'] = '';
2064
	if (!empty($settings['theme_variants']))
2065
	{
2066
		// Overriding - for previews and that ilk.
2067
		if (!empty($_REQUEST['variant']))
2068
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2069
		// User selection?
2070
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2071
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2072
		// If not a user variant, select the default.
2073
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2074
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2075
2076
		// Do this to keep things easier in the templates.
2077
		$context['theme_variant'] = '_' . $context['theme_variant'];
2078
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2079
2080
		if (!empty($context['theme_variant']))
2081
		{
2082
			loadCSSFile('index' . $context['theme_variant'] . '.css', array(), 'smf_index' . $context['theme_variant']);
2083
			if ($context['right_to_left'])
2084
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array(), 'smf_rtl' . $context['theme_variant']);
2085
		}
2086
	}
2087
2088
	// Let's be compatible with old themes!
2089
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2090
		$context['template_layers'] = array('main');
2091
2092
	$context['tabindex'] = 1;
2093
2094
	// Compatibility.
2095
	if (!isset($settings['theme_version']))
2096
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2097
2098
	// Default JS variables for use in every theme
2099
	$context['javascript_vars'] = array(
2100
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2101
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2102
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2103
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2104
		'smf_scripturl' => '"' . $scripturl . '"',
2105
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2106
		'smf_charset' => '"' . $context['character_set'] . '"',
2107
		'smf_session_id' => '"' . $context['session_id'] . '"',
2108
		'smf_session_var' => '"' . $context['session_var'] . '"',
2109
		'smf_member_id' => $context['user']['id'],
2110
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2111
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2112
	);
2113
2114
	// Add the JQuery library to the list of files to load.
2115
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2116
		loadJavascriptFile('https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js', array('external' => true), 'smf_jquery');
2117
2118
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2119
		loadJavascriptFile('jquery-2.1.4.min.js', array('seed' => false), 'smf_jquery');
2120
2121
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2122
		loadJavascriptFile($modSettings['jquery_custom'], array(), 'smf_jquery');
2123
2124
	// Auto loading? template_javascript() will take care of the local half of this.
2125
	else
2126
		loadJavascriptFile('https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js', array('external' => true), 'smf_jquery');
2127
2128
	// Queue our JQuery plugins!
2129
	loadJavascriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2130
	if (!$user_info['is_guest'])
2131
	{
2132
		loadJavascriptFile('jquery.custom-scrollbar.js', array(), 'smf_jquery_scrollbar');
2133
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2134
	}
2135
2136
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2137
	loadJavascriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2138
	loadJavascriptFile('theme.js', array('minimize' => true), 'smf_theme');
2139
2140
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2141
	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())
2142
	{
2143
		if (isBrowser('possibly_robot'))
2144
		{
2145
			// @todo Maybe move this somewhere better?!
2146
			require_once($sourcedir . '/ScheduledTasks.php');
2147
2148
			// What to do, what to do?!
2149
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2150
				AutoTask();
2151
			else
2152
				ReduceMailQueue();
2153
		}
2154
		else
2155
		{
2156
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2157
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
2158
2159
			addInlineJavascript('
2160
		function smfAutoTask()
2161
		{
2162
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2163
		}
2164
		window.setTimeout("smfAutoTask();", 1);');
2165
		}
2166
	}
2167
2168
	// And we should probably trigger the cron too.
2169
	if (empty($modSettings['cron_is_real_cron']))
2170
	{
2171
		$ts = time();
2172
		$ts -= $ts % 15;
2173
		addInlineJavaScript('
2174
	function triggerCron()
2175
	{
2176
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2177
	}
2178
	window.setTimeout(triggerCron, 1);', true);
2179
	}
2180
2181
	// Filter out the restricted boards from the linktree
2182
	if (!$user_info['is_admin'] && !empty($board))
2183
	{
2184
		foreach ($context['linktree'] as $k => $element)
2185
		{
2186
			if (!empty($element['groups']) &&
2187
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2188
				(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2189
			{
2190
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2191
				$context['linktree'][$k]['extra_before'] = '<i>';
2192
				$context['linktree'][$k]['extra_after'] = '</i>';
2193
				unset($context['linktree'][$k]['url']);
2194
			}
2195
		}
2196
	}
2197
2198
	// Any files to include at this point?
2199 View Code Duplication
	if (!empty($modSettings['integrate_theme_include']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2200
	{
2201
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2202
		foreach ($theme_includes as $include)
2203
		{
2204
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2205
			if (file_exists($include))
2206
				require_once($include);
2207
		}
2208
	}
2209
2210
	// Call load theme integration functions.
2211
	call_integration_hook('integrate_load_theme');
2212
2213
	// We are ready to go.
2214
	$context['theme_loaded'] = true;
2215
}
2216
2217
/**
2218
 * Load a template - if the theme doesn't include it, use the default.
2219
 * What this function does:
2220
 *  - loads a template file with the name template_name from the current, default, or base theme.
2221
 *  - detects a wrong default theme directory and tries to work around it.
2222
 *
2223
 * @uses the template_include() function to include the file.
2224
 * @param string $template_name The name of the template to load
2225
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2226
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2227
 * @return boolean Whether or not the template was loaded
2228
 */
2229
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2230
{
2231
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
2232
2233
	// Do any style sheets first, cause we're easy with those.
2234
	if (!empty($style_sheets))
2235
	{
2236
		if (!is_array($style_sheets))
2237
			$style_sheets = array($style_sheets);
2238
2239
		foreach ($style_sheets as $sheet)
2240
			loadCSSFile($sheet . '.css', array(), $sheet);
2241
	}
2242
2243
	// No template to load?
2244
	if ($template_name === false)
2245
		return true;
2246
2247
	$loaded = false;
2248
	foreach ($settings['template_dirs'] as $template_dir)
2249
	{
2250
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2251
		{
2252
			$loaded = true;
2253
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2254
			break;
2255
		}
2256
	}
2257
2258
	if ($loaded)
2259
	{
2260
		// For compatibility reasons, if this is the index template without new functions, include compatible stuff.
2261
		if (substr($template_name, 0, 5) == 'index' && !function_exists('template_button_strip'))
2262
			loadTemplate('Compat');
2263
2264
		if ($db_show_debug === true)
2265
			$context['debug']['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 2248. 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...
2266
2267
		// If they have specified an initialization function for this template, go ahead and call it now.
2268
		if (function_exists('template_' . $template_name . '_init'))
2269
			call_user_func('template_' . $template_name . '_init');
2270
	}
2271
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2272
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2273
	{
2274
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2275
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2276
2277 View Code Duplication
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2278
		{
2279
			loadLanguage('Errors');
2280
			echo '
2281
<div class="alert errorbox">
2282
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2283
</div>';
2284
		}
2285
2286
		loadTemplate($template_name);
2287
	}
2288
	// Cause an error otherwise.
2289
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2290
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2291
	elseif ($fatal)
2292
		die(log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load Themes/default/%s.template.php!', (string) $template_name), 'template'));
2293
	else
2294
		return false;
2295
}
2296
2297
2298
/**
2299
 * Load a sub-template.
2300
 * What it does:
2301
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2302
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2303
 *	for debugging purposes.
2304
 *
2305
 * @todo get rid of reading $_REQUEST directly
2306
 *
2307
 * @param string $sub_template_name The name of the sub-template to load
2308
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2309
 */
2310
function loadSubTemplate($sub_template_name, $fatal = false)
2311
{
2312
	global $context, $txt, $db_show_debug;
2313
2314
	if ($db_show_debug === true)
2315
		$context['debug']['sub_templates'][] = $sub_template_name;
2316
2317
	// Figure out what the template function is named.
2318
	$theme_function = 'template_' . $sub_template_name;
2319
	if (function_exists($theme_function))
2320
		$theme_function();
2321
	elseif ($fatal === false)
2322
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2323
	elseif ($fatal !== 'ignore')
2324
		die(log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load the %s sub template!', (string) $sub_template_name), 'template'));
2325
2326
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2327
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2328
	{
2329
		echo '
2330
<div class="warningbox">---- ', $sub_template_name, ' ends ----</div>';
2331
	}
2332
}
2333
2334
/**
2335
 * Add a CSS file for output later
2336
 *
2337
 * @param string $fileName The name of the file to load
2338
 * @param array $params An array of parameters
2339
 * Keys are the following:
2340
 * 	- ['external'] (true/false): define if the file is a externally located file. Needs to be set to true if you are loading an external file
2341
 * 	- ['default_theme'] (true/false): force use of default theme url
2342
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the default theme if not found in the current theme
2343
 *  - ['validate'] (true/false): if true script will validate the local file exists
2344
 *  - ['rtl'] (string): additional file to load in RTL mode
2345
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2346
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2347
 * @param string $id An ID to stick on the end of the filename for caching purposes
2348
 */
2349
function loadCSSFile($fileName, $params = array(), $id = '')
2350
{
2351
	global $settings, $context, $modSettings;
2352
2353
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ? (array_key_exists('browser_cache', $modSettings) ? $modSettings['browser_cache'] : '') : (is_string($params['seed']) ? ($params['seed'] = $params['seed'][0] === '?' ? $params['seed'] : '?' . $params['seed']) : '');
2354
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2355
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2356
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2357
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2358
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2359
2360
	// If this is an external file, automatically set this to false.
2361
	if (!empty($params['external']))
2362
		$params['minimize'] = false;
2363
2364
	// Account for shorthand like admin.css?alp21 filenames
2365
	$has_seed = strpos($fileName, '.css?');
2366
	$id = empty($id) ? strtr(basename(str_replace('.css', '', $fileName)), '?', '_') : $id;
2367
2368
	// Is this a local file?
2369 View Code Duplication
	if (empty($params['external']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2370
	{
2371
		// Are we validating the the file exists?
2372
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/css/' . $fileName))
2373
		{
2374
			// Maybe the default theme has it?
2375
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/css/' . $fileName))
2376
			{
2377
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2378
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2379
			}
2380
2381
			else
2382
				$fileUrl = false;
2383
		}
2384
2385
		else
2386
		{
2387
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2388
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2389
		}
2390
	}
2391
2392
	// An external file doesn't have a filepath. Mock one for simplicity.
2393
	else
2394
	{
2395
		$fileUrl = $fileName;
2396
		$filePath = $fileName;
2397
	}
2398
2399
	// Add it to the array for use in the template
2400 View Code Duplication
	if (!empty($fileName))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2401
		$context['css_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params);
0 ignored issues
show
Bug introduced by
The variable $filePath 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...
2402
2403
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2404
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2405
}
2406
2407
/**
2408
 * Add a block of inline css code to be executed later
2409
 *
2410
 * - only use this if you have to, generally external css files are better, but for very small changes
2411
 *   or for scripts that require help from PHP/whatever, this can be useful.
2412
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2413
 *
2414
 * @param string $css Some css code
2415
 * @return void|bool Adds the CSS to the $context['css_header'] array or returns if no CSS is specified
2416
 */
2417
function addInlineCss($css)
2418
{
2419
	global $context;
2420
2421
	// Gotta add something...
2422
	if (empty($css))
2423
		return false;
2424
2425
	$context['css_header'][] = $css;
2426
}
2427
2428
/**
2429
 * Add a Javascript file for output later
2430
2431
 * @param string $filename The name of the file to load
0 ignored issues
show
Documentation introduced by
There is no parameter named $filename. Did you maybe mean $fileName?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
2432
 * @param array $params An array of parameter info
2433
 * Keys are the following:
2434
 * 	- ['external'] (true/false): define if the file is a externally located file. Needs to be set to true if you are loading an external file
2435
 * 	- ['default_theme'] (true/false): force use of default theme url
2436
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2437
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2438
 *	default theme if not found in the current theme
2439
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2440
 *  - ['validate'] (true/false): if true script will validate the local file exists
2441
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2442
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2443
 *
2444
 * @param string $id An ID to stick on the end of the filename
2445
 */
2446
function loadJavascriptFile($fileName, $params = array(), $id = '')
2447
{
2448
	global $settings, $context, $modSettings;
2449
2450
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ? (array_key_exists('browser_cache', $modSettings) ? $modSettings['browser_cache'] : '') : (is_string($params['seed']) ? ($params['seed'] = $params['seed'][0] === '?' ? $params['seed'] : '?' . $params['seed']) : '');
2451
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2452
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2453
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2454
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2455
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2456
2457
	// If this is an external file, automatically set this to false.
2458
	if (!empty($params['external']))
2459
		$params['minimize'] = false;
2460
2461
	// Account for shorthand like admin.js?alp21 filenames
2462
	$has_seed = strpos($fileName, '.js?');
2463
	$id = empty($id) ? strtr(basename(str_replace('.js', '', $fileName)), '?', '_') : $id;
2464
2465
	// Is this a local file?
2466 View Code Duplication
	if (empty($params['external']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2467
	{
2468
		// Are we validating it exists on disk?
2469
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/scripts/' . $fileName))
2470
		{
2471
			// Can't find it in this theme, how about the default?
2472
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/scripts/' . $fileName))
2473
			{
2474
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2475
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2476
			}
2477
2478
			else
2479
			{
2480
				$fileUrl = false;
2481
				$filePath = false;
2482
			}
2483
		}
2484
2485
		else
2486
		{
2487
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2488
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2489
		}
2490
	}
2491
2492
	// An external file doesn't have a filepath. Mock one for simplicity.
2493
	else
2494
	{
2495
		$fileUrl = $fileName;
2496
		$filePath = $fileName;
2497
	}
2498
2499
	// Add it to the array for use in the template
2500 View Code Duplication
	if (!empty($fileName))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2501
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params);
2502
}
2503
2504
/**
2505
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2506
 * Cleaner and easier (for modders) than to use the function below.
2507
 *
2508
 * @param string $key The key for this variable
2509
 * @param string $value The value
2510
 * @param bool $escape Whether or not to escape the value
2511
 */
2512
function addJavascriptVar($key, $value, $escape = false)
2513
{
2514
	global $context;
2515
2516
	if (!empty($key) && (!empty($value) || $value === '0'))
2517
		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
2518
}
2519
2520
/**
2521
 * Add a block of inline Javascript code to be executed later
2522
 *
2523
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2524
 *   or for scripts that require help from PHP/whatever, this can be useful.
2525
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2526
 *
2527
 * @param string $javascript Some JS code
2528
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2529
 * @return void|bool Adds the code to one of the $context['javascript_inline'] arrays or returns if no JS was specified
2530
 */
2531
function addInlineJavascript($javascript, $defer = false)
2532
{
2533
	global $context;
2534
2535
	if (empty($javascript))
2536
		return false;
2537
2538
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2539
}
2540
2541
/**
2542
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2543
 *
2544
 * @param string $template_name The name of a template file
2545
 * @param string $lang A specific language to load this file from
2546
 * @param bool $fatal Whether to die with an error if it can't be loaded
2547
 * @param bool $force_reload Whether to load the file again if it's already loaded
2548
 * @return string The language actually loaded.
2549
 */
2550
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2551
{
2552
	global $user_info, $language, $settings, $context, $modSettings;
2553
	global $db_show_debug, $sourcedir, $txt, $birthdayEmails, $txtBirthdayEmails;
2554
	static $already_loaded = array();
2555
2556
	// Default to the user's language.
2557
	if ($lang == '')
2558
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2559
2560
	// Do we want the English version of language file as fallback?
2561
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2562
		loadLanguage($template_name, 'english', false);
2563
2564
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2565
		return $lang;
2566
2567
	// Make sure we have $settings - if not we're in trouble and need to find it!
2568
	if (empty($settings['default_theme_dir']))
2569
	{
2570
		require_once($sourcedir . '/ScheduledTasks.php');
2571
		loadEssentialThemeData();
2572
	}
2573
2574
	// What theme are we in?
2575
	$theme_name = basename($settings['theme_url']);
2576
	if (empty($theme_name))
2577
		$theme_name = 'unknown';
2578
2579
	// For each file open it up and write it out!
2580
	foreach (explode('+', $template_name) as $template)
2581
	{
2582
		// Obviously, the current theme is most important to check.
2583
		$attempts = array(
2584
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2585
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2586
		);
2587
2588
		// Do we have a base theme to worry about?
2589
		if (isset($settings['base_theme_dir']))
2590
		{
2591
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2592
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2593
		}
2594
2595
		// Fall back on the default theme if necessary.
2596
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2597
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2598
2599
		// Fall back on the English language if none of the preferred languages can be found.
2600
		if (!in_array('english', array($lang, $language)))
2601
		{
2602
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2603
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2604
		}
2605
2606
		// Try to find the language file.
2607
		$found = false;
2608
		foreach ($attempts as $k => $file)
2609
		{
2610
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2611
			{
2612
				// Include it!
2613
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2614
2615
				// Note that we found it.
2616
				$found = true;
2617
2618
				break;
2619
			}
2620
		}
2621
2622
		// That couldn't be found!  Log the error, but *try* to continue normally.
2623
		if (!$found && $fatal)
2624
		{
2625
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2626
			break;
2627
		}
2628
2629
		// For the sake of backward compatibility
2630
		if (!empty($txt['emails']))
2631
		{
2632
			foreach ($txt['emails'] as $key => $value)
2633
			{
2634
				$txt[$key . '_subject'] = $value['subject'];
2635
				$txt[$key . '_body'] = $value['body'];
2636
			}
2637
			$txt['emails'] = array();
2638
		}
2639
		// For sake of backward compatibility: $birthdayEmails is supposed to be
2640
		// empty in a normal install. If it isn't it means the forum is using
2641
		// something "old" (it may be the translation, it may be a mod) and this
2642
		// code (like the piece above) takes care of converting it to the new format
2643
		if (!empty($birthdayEmails))
2644
		{
2645
			foreach ($birthdayEmails as $key => $value)
2646
			{
2647
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
2648
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
2649
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
2650
			}
2651
			$birthdayEmails = array();
2652
		}
2653
	}
2654
2655
	// Keep track of what we're up to soldier.
2656
	if ($db_show_debug === true)
2657
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
2658
2659
	// Remember what we have loaded, and in which language.
2660
	$already_loaded[$template_name] = $lang;
2661
2662
	// Return the language actually loaded.
2663
	return $lang;
2664
}
2665
2666
/**
2667
 * Get all parent boards (requires first parent as parameter)
2668
 * It finds all the parents of id_parent, and that board itself.
2669
 * Additionally, it detects the moderators of said boards.
2670
 *
2671
 * @param int $id_parent The ID of the parent board
2672
 * @return array An array of information about the boards found.
2673
 */
2674
function getBoardParents($id_parent)
2675
{
2676
	global $scripturl, $smcFunc;
2677
2678
	// First check if we have this cached already.
2679
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
2680
	{
2681
		$boards = array();
2682
		$original_parent = $id_parent;
2683
2684
		// Loop while the parent is non-zero.
2685
		while ($id_parent != 0)
2686
		{
2687
			$result = $smcFunc['db_query']('', '
2688
				SELECT
2689
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
2690
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
2691
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
2692
				FROM {db_prefix}boards AS b
2693
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
2694
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
2695
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
2696
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
2697
				WHERE b.id_board = {int:board_parent}',
2698
				array(
2699
					'board_parent' => $id_parent,
2700
				)
2701
			);
2702
			// In the EXTREMELY unlikely event this happens, give an error message.
2703
			if ($smcFunc['db_num_rows']($result) == 0)
2704
				fatal_lang_error('parent_not_found', 'critical');
2705
			while ($row = $smcFunc['db_fetch_assoc']($result))
2706
			{
2707
				if (!isset($boards[$row['id_board']]))
2708
				{
2709
					$id_parent = $row['id_parent'];
2710
					$boards[$row['id_board']] = array(
2711
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
2712
						'name' => $row['name'],
2713
						'level' => $row['child_level'],
2714
						'groups' => explode(',', $row['member_groups']),
2715
						'deny_groups' => explode(',', $row['deny_member_groups']),
2716
						'moderators' => array(),
2717
						'moderator_groups' => array()
2718
					);
2719
				}
2720
				// If a moderator exists for this board, add that moderator for all children too.
2721 View Code Duplication
				if (!empty($row['id_moderator']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2722
					foreach ($boards as $id => $dummy)
2723
					{
2724
						$boards[$id]['moderators'][$row['id_moderator']] = array(
2725
							'id' => $row['id_moderator'],
2726
							'name' => $row['real_name'],
2727
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
2728
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
2729
						);
2730
					}
2731
2732
				// If a moderator group exists for this board, add that moderator group for all children too
2733 View Code Duplication
				if (!empty($row['id_moderator_group']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2734
					foreach ($boards as $id => $dummy)
2735
					{
2736
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
2737
							'id' => $row['id_moderator_group'],
2738
							'name' => $row['group_name'],
2739
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
2740
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
2741
						);
2742
					}
2743
			}
2744
			$smcFunc['db_free_result']($result);
2745
		}
2746
2747
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
2748
	}
2749
2750
	return $boards;
2751
}
2752
2753
/**
2754
 * Attempt to reload our known languages.
2755
 * It will try to choose only utf8 or non-utf8 languages.
2756
 *
2757
 * @param bool $use_cache Whether or not to use the cache
2758
 * @param bool $favor_utf8 Whether or not to favor UTF-8 files
2759
 * @return array An array of information about available languages
2760
 */
2761
function getLanguages($use_cache = true, $favor_utf8 = true)
2762
{
2763
	global $context, $smcFunc, $settings, $modSettings;
2764
2765
	// Either we don't use the cache, or its expired.
2766
	if (!$use_cache || ($context['languages'] = cache_get_data('known_languages' . ($favor_utf8 ? '' : '_all'), !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600)) == null)
2767
	{
2768
		// If we don't have our ucwords function defined yet, let's load the settings data.
2769
		if (empty($smcFunc['ucwords']))
2770
			reloadSettings();
2771
2772
		// If we don't have our theme information yet, let's get it.
2773
		if (empty($settings['default_theme_dir']))
2774
			loadTheme(0, false);
2775
2776
		// Default language directories to try.
2777
		$language_directories = array(
2778
			$settings['default_theme_dir'] . '/languages',
2779
		);
2780
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
2781
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
2782
2783
		// We possibly have a base theme directory.
2784
		if (!empty($settings['base_theme_dir']))
2785
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
2786
2787
		// Remove any duplicates.
2788
		$language_directories = array_unique($language_directories);
2789
2790
		// Get a list of languages.
2791
		$langList = !empty($modSettings['langList']) ? json_decode($modSettings['langList'], true) : array();
2792
		$langList = is_array($langList) ? $langList : false;
2793
2794
		$catchLang = array();
2795
2796
		foreach ($language_directories as $language_dir)
2797
		{
2798
			// Can't look in here... doesn't exist!
2799
			if (!file_exists($language_dir))
2800
				continue;
2801
2802
			$dir = dir($language_dir);
2803
			while ($entry = $dir->read())
2804
			{
2805
				// Look for the index language file....
2806
				if (!preg_match('~^index\.(.+)\.php$~', $entry, $matches))
2807
					continue;
2808
2809
				if (!empty($langList) && !empty($langList[$matches[1]]))
2810
					$langName = $langList[$matches[1]];
2811
2812
				else
2813
				{
2814
					$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
2815
2816
					// Get the line we need.
2817
					$fp = @fopen($language_dir .'/'. $entry);
2818
2819
					// Yay!
2820
					if ($fp)
2821
					{
2822
						while (($line = fgets($fp)) !== false)
2823
						{
2824
							preg_match('~\$txt\[\'native_name\'\] = \'(.+)\'\;~', $line, $matchNative);
2825
2826
							// Set the language's name.
2827
							if (!empty($matchNative) && !empty($matchNative[1]))
2828
							{
2829
								$langName = un_htmlspecialchars($matchNative[1]);
2830
								break;
2831
							}
2832
						}
2833
2834
						fclose($fp);
2835
					}
2836
2837
					// Catch the language name.
2838
					$catchLang[$matches[1]] = $langName;
2839
				}
2840
2841
				// Build this language entry.
2842
				$context['languages'][$matches[1]] = array(
2843
					'name' => $langName,
2844
					'selected' => false,
2845
					'filename' => $matches[1],
2846
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
2847
				);
2848
2849
				$indexFile = '';
0 ignored issues
show
Unused Code introduced by
$indexFile is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2850
			}
2851
			$dir->close();
2852
		}
2853
2854
		// Do we need to store the lang list?
2855
		if (empty($langList))
2856
			updateSettings(array('langList' => json_encode($catchLang)));
2857
2858
		// Favoring UTF8? Then prevent us from selecting non-UTF8 versions.
2859
		if ($favor_utf8)
2860
		{
2861
			foreach ($context['languages'] as $lang)
0 ignored issues
show
Bug introduced by
The expression $context['languages'] of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2862
				if (substr($lang['filename'], strlen($lang['filename']) - 5, 5) != '-utf8' && isset($context['languages'][$lang['filename'] . '-utf8']))
2863
					unset($context['languages'][$lang['filename']]);
2864
		}
2865
2866
		// Let's cash in on this deal.
2867
		if (!empty($modSettings['cache_enable']))
2868
			cache_put_data('known_languages' . ($favor_utf8 ? '' : '_all'), $context['languages'], !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
2869
	}
2870
2871
	return $context['languages'];
2872
}
2873
2874
/**
2875
 * Replace all vulgar words with respective proper words. (substring or whole words..)
2876
 * What this function does:
2877
 *  - it censors the passed string.
2878
 *  - if the theme setting allow_no_censored is on, and the theme option
2879
 *	show_no_censored is enabled, does not censor, unless force is also set.
2880
 *  - it caches the list of censored words to reduce parsing.
2881
 *
2882
 * @param string &$text The text to censor
2883
 * @param bool $force Whether to censor the text regardless of settings
2884
 * @return string The censored text
2885
 */
2886
function censorText(&$text, $force = false)
2887
{
2888
	global $modSettings, $options, $txt;
2889
	static $censor_vulgar = null, $censor_proper;
2890
2891
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
2892
		return $text;
2893
2894
	// If they haven't yet been loaded, load them.
2895
	if ($censor_vulgar == null)
2896
	{
2897
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
2898
		$censor_proper = explode("\n", $modSettings['censor_proper']);
2899
2900
		// Quote them for use in regular expressions.
2901
		if (!empty($modSettings['censorWholeWord']))
2902
		{
2903
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
2904
			{
2905
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
2906
				$censor_vulgar[$i] = '/(?<=^|\W)' . $censor_vulgar[$i] . '(?=$|\W)/' . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ((empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8' ? 'u' : '');
2907
2908
				// @todo I'm thinking the old way is some kind of bug and this is actually fixing it.
2909
				//if (strpos($censor_vulgar[$i], '\'') !== false)
2910
					//$censor_vulgar[$i] = str_replace('\'', '&#039;', $censor_vulgar[$i]);
2911
			}
2912
		}
2913
	}
2914
2915
	// Censoring isn't so very complicated :P.
2916
	if (empty($modSettings['censorWholeWord']))
2917
	{
2918
		$func = !empty($modSettings['censorIgnoreCase']) ? 'str_ireplace' : 'str_replace';
2919
		$text = $func($censor_vulgar, $censor_proper, $text);
2920
	}
2921
	else
2922
		$text = preg_replace($censor_vulgar, $censor_proper, $text);
2923
2924
	return $text;
2925
}
2926
2927
/**
2928
 * Load the template/language file using eval or require? (with eval we can show an error message!)
2929
 * 	- loads the template or language file specified by filename.
2930
 * 	- uses eval unless disableTemplateEval is enabled.
2931
 * 	- outputs a parse error if the file did not exist or contained errors.
2932
 * 	- attempts to detect the error and line, and show detailed information.
2933
 *
2934
 * @param string $filename The name of the file to include
2935
 * @param bool $once If true only includes the file once (like include_once)
2936
 */
2937
function template_include($filename, $once = false)
2938
{
2939
	global $context, $settings, $txt, $scripturl, $modSettings;
2940
	global $boardurl, $boarddir, $sourcedir;
2941
	global $maintenance, $mtitle, $mmessage;
2942
	static $templates = array();
2943
2944
	// We want to be able to figure out any errors...
2945
	@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...
2946
2947
	// Don't include the file more than once, if $once is true.
2948
	if ($once && in_array($filename, $templates))
2949
		return;
2950
	// Add this file to the include list, whether $once is true or not.
2951
	else
2952
		$templates[] = $filename;
2953
2954
	// Are we going to use eval?
2955
	if (empty($modSettings['disableTemplateEval']))
2956
	{
2957
		$file_found = file_exists($filename) && eval('?' . '>' . rtrim(file_get_contents($filename))) !== false;
2958
		$settings['current_include_filename'] = $filename;
2959
	}
2960
	else
2961
	{
2962
		$file_found = file_exists($filename);
2963
2964
		if ($once && $file_found)
2965
			require_once($filename);
2966
		elseif ($file_found)
2967
			require($filename);
2968
	}
2969
2970
	if ($file_found !== true)
2971
	{
2972
		ob_end_clean();
2973
		if (!empty($modSettings['enableCompressedOutput']))
2974
			@ob_start('ob_gzhandler');
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...
2975
		else
2976
			ob_start();
2977
2978 View Code Duplication
		if (isset($_GET['debug']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2979
			header('Content-Type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
2980
2981
		// Don't cache error pages!!
2982
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
2983
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
2984
		header('Cache-Control: no-cache');
2985
2986
		if (!isset($txt['template_parse_error']))
2987
		{
2988
			$txt['template_parse_error'] = 'Template Parse Error!';
2989
			$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>.';
2990
			$txt['template_parse_error_details'] = 'There was a problem loading the <pre><strong>%1$s</strong></pre> template or language file.  Please check the syntax and try again - remember, single quotes (<pre>\'</pre>) often have to be escaped with a slash (<pre>\\</pre>).  To see more specific error information from PHP, try <a href="' . $boardurl . '%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="' . $scripturl . '?theme=1">use the default theme</a>.';
2991
			$txt['template_parse_errmsg'] = 'Unfortunately more information is not available at this time as to exactly what is wrong.';
2992
		}
2993
2994
		// First, let's get the doctype and language information out of the way.
2995
		echo '<!DOCTYPE html>
2996
<html', !empty($context['right_to_left']) ? ' dir="rtl"' : '', '>
2997
	<head>';
2998
		if (isset($context['character_set']))
2999
			echo '
3000
		<meta charset="', $context['character_set'], '">';
3001
3002
		if (!empty($maintenance) && !allowedTo('admin_forum'))
3003
			echo '
3004
		<title>', $mtitle, '</title>
3005
	</head>
3006
	<body>
3007
		<h3>', $mtitle, '</h3>
3008
		', $mmessage, '
3009
	</body>
3010
</html>';
3011
		elseif (!allowedTo('admin_forum'))
3012
			echo '
3013
		<title>', $txt['template_parse_error'], '</title>
3014
	</head>
3015
	<body>
3016
		<h3>', $txt['template_parse_error'], '</h3>
3017
		', $txt['template_parse_error_message'], '
3018
	</body>
3019
</html>';
3020
		else
3021
		{
3022
			require_once($sourcedir . '/Subs-Package.php');
3023
3024
			$error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3025
			if (empty($error) && ini_get('track_errors') && !empty($php_errormsg))
3026
				$error = $php_errormsg;
3027
			if (empty($error))
3028
				$error = $txt['template_parse_errmsg'];
3029
3030
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
3031
3032
			echo '
3033
		<title>', $txt['template_parse_error'], '</title>
3034
	</head>
3035
	<body>
3036
		<h3>', $txt['template_parse_error'], '</h3>
3037
		', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3038
3039
			if (!empty($error))
3040
				echo '
3041
		<hr>
3042
3043
		<div style="margin: 0 20px;"><pre>', strtr(strtr($error, array('<strong>' . $boarddir => '<strong>...', '<strong>' . strtr($boarddir, '\\', '/') => '<strong>...')), '\\', '/'), '</pre></div>';
3044
3045
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
3046
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
3047
			{
3048
				$data = file($filename);
3049
				$data2 = highlight_php_code(implode('', $data));
3050
				$data2 = preg_split('~\<br( /)?\>~', $data2);
3051
3052
				// Fix the PHP code stuff...
3053
				if (!isBrowser('gecko'))
3054
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
3055
				else
3056
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
3057
3058
				// Now we get to work around a bug in PHP where it doesn't escape <br>s!
3059
				$j = -1;
3060
				foreach ($data as $line)
3061
				{
3062
					$j++;
3063
3064
					if (substr_count($line, '<br>') == 0)
3065
						continue;
3066
3067
					$n = substr_count($line, '<br>');
3068
					for ($i = 0; $i < $n; $i++)
3069
					{
3070
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
3071
						unset($data2[$j + $i + 1]);
3072
					}
3073
					$j += $n;
3074
				}
3075
				$data2 = array_values($data2);
3076
				array_unshift($data2, '');
3077
3078
				echo '
3079
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
3080
3081
				// Figure out what the color coding was before...
3082
				$line = max($match[1] - 9, 1);
3083
				$last_line = '';
3084
				for ($line2 = $line - 1; $line2 > 1; $line2--)
3085
					if (strpos($data2[$line2], '<') !== false)
3086
					{
3087
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
3088
							$last_line = $color_match[1];
3089
						break;
3090
					}
3091
3092
				// Show the relevant lines...
3093
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
3094
				{
3095
					if ($line == $match[1])
3096
						echo '</pre><div style="background-color: #ffb0b5;"><pre style="margin: 0;">';
3097
3098
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
3099
					if (isset($data2[$line]) && $data2[$line] != '')
3100
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
3101
3102
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
3103
					{
3104
						$last_line = $color_match[1];
3105
						echo '</', substr($last_line, 1, 4), '>';
3106
					}
3107
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
3108
						$last_line = '';
3109
					elseif ($last_line != '' && $data2[$line] != '')
3110
						echo '</', substr($last_line, 1, 4), '>';
3111
3112
					if ($line == $match[1])
3113
						echo '</pre></div><pre style="margin: 0;">';
3114
					else
3115
						echo "\n";
3116
				}
3117
3118
				echo '</pre></div>';
3119
			}
3120
3121
			echo '
3122
	</body>
3123
</html>';
3124
		}
3125
3126
		die;
3127
	}
3128
}
3129
3130
/**
3131
 * Initialize a database connection.
3132
 */
3133
function loadDatabase()
3134
{
3135
	global $db_persist, $db_connection, $db_server, $db_user, $db_passwd;
3136
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $sourcedir, $db_prefix, $db_port;
3137
3138
	// Figure out what type of database we are using.
3139
	if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
3140
		$db_type = 'mysql';
3141
3142
	// Load the file for the database.
3143
	require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
3144
3145
	$db_options = array();
3146
3147
	// Add in the port if needed
3148
	if (!empty($db_port))
3149
		$db_options['port'] = $db_port;
3150
3151
	// If we are in SSI try them first, but don't worry if it doesn't work, we have the normal username and password we can use.
3152
	if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
3153
	{
3154
		$options = array_merge($db_options, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true));
3155
3156
		$db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, $options);
3157
	}
3158
3159
	// Either we aren't in SSI mode, or it failed.
3160
	if (empty($db_connection))
3161
	{
3162
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
3163
3164
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
3165
	}
3166
3167
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
3168
	if (!$db_connection)
3169
		display_db_error();
3170
3171
	// If in SSI mode fix up the prefix.
3172
	if (SMF == 'SSI')
3173
		db_fix_prefix($db_prefix, $db_name);
0 ignored issues
show
Unused Code introduced by
The call to the function db_fix_prefix() seems unnecessary as the function has no side-effects.
Loading history...
3174
}
3175
3176
/**
3177
 * Try to retrieve a cache entry. On failure, call the appropriate function.
3178
 *
3179
 * @param string $key The key for this entry
3180
 * @param string $file The file associated with this entry
3181
 * @param string $function The function to call
3182
 * @param array $params Parameters to be passed to the specified function
3183
 * @param int $level The cache level
3184
 * @return string The cached data
3185
 */
3186
function cache_quick_get($key, $file, $function, $params, $level = 1)
3187
{
3188
	global $modSettings, $sourcedir;
3189
3190
	// @todo Why are we doing this if caching is disabled?
3191
3192
	if (function_exists('call_integration_hook'))
3193
		call_integration_hook('pre_cache_quick_get', array(&$key, &$file, &$function, &$params, &$level));
3194
3195
	/* Refresh the cache if either:
3196
		1. Caching is disabled.
3197
		2. The cache level isn't high enough.
3198
		3. The item has not been cached or the cached item expired.
3199
		4. The cached item has a custom expiration condition evaluating to true.
3200
		5. The expire time set in the cache item has passed (needed for Zend).
3201
	*/
3202
	if (empty($modSettings['cache_enable']) || $modSettings['cache_enable'] < $level || !is_array($cache_block = cache_get_data($key, 3600)) || (!empty($cache_block['refresh_eval']) && eval($cache_block['refresh_eval'])) || (!empty($cache_block['expires']) && $cache_block['expires'] < time()))
3203
	{
3204
		require_once($sourcedir . '/' . $file);
3205
		$cache_block = call_user_func_array($function, $params);
3206
3207
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= $level)
3208
			cache_put_data($key, $cache_block, $cache_block['expires'] - time());
3209
	}
3210
3211
	// Some cached data may need a freshening up after retrieval.
3212
	if (!empty($cache_block['post_retri_eval']))
3213
		eval($cache_block['post_retri_eval']);
3214
3215
	if (function_exists('call_integration_hook'))
3216
		call_integration_hook('post_cache_quick_get', array(&$cache_block));
3217
3218
	return $cache_block['data'];
3219
}
3220
3221
/**
3222
 * Puts value in the cache under key for ttl seconds.
3223
 *
3224
 * - It may "miss" so shouldn't be depended on
3225
 * - Uses the cache engine chosen in the ACP and saved in settings.php
3226
 * - It supports:
3227
 *	 Xcache: http://xcache.lighttpd.net/wiki/XcacheApi
3228
 *	 memcache: http://www.php.net/memcache
3229
 *	 APC: http://www.php.net/apc
3230
 *	 Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
3231
 *	 Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
3232
 *
3233
 * @param string $key A key for this value
3234
 * @param mixed $value The data to cache
3235
 * @param int $ttl How long (in seconds) the data should be cached for
3236
 */
3237
function cache_put_data($key, $value, $ttl = 120)
3238
{
3239
	global $boardurl, $modSettings, $memcached;
3240
	global $cache_hits, $cache_count, $db_show_debug, $cachedir;
3241
	global $cache_accelerator, $cache_enable, $cache_memcached;
3242
3243
	if (empty($cache_enable))
3244
		return;
3245
3246
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3247
	if (isset($db_show_debug) && $db_show_debug === true)
3248
	{
3249
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'put', 's' => $value === null ? 0 : strlen(json_encode($value)));
3250
		$st = microtime();
3251
	}
3252
3253
	$key = md5($boardurl . filemtime($cachedir . '/' . 'index.php')) . '-SMF-' . strtr($key, ':/', '-_');
3254
	$value = $value === null ? null : json_encode($value);
3255
3256
	switch ($cache_accelerator)
3257
	{
3258
		case 'memcached':
3259
			// The simple yet efficient memcached.
3260
			if ((function_exists('memcached_set') || function_exists('memcache_set')) && isset($cache_memcached) && trim($cache_memcached) != '')
3261
			{
3262
				// Not connected yet?
3263
				if (empty($memcached))
3264
					get_memcached_server();
3265
				if (!$memcached)
3266
					return;
3267
3268
				memcache_set($memcached, $key, $value, 0, $ttl);
3269
			}
3270
			break;
3271
		case 'apc':
3272
			// Alternative PHP Cache, ahoy!
3273
			if (function_exists('apc_store'))
3274
			{
3275
				// An extended key is needed to counteract a bug in APC.
3276
				if ($value === null)
3277
					apc_delete($key . 'smf');
3278
				else
3279
					apc_store($key . 'smf', $value, $ttl);
3280
			}
3281
			break;
3282 View Code Duplication
		case 'zend':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3283
			// Zend Platform/ZPS/etc.
3284
			if (function_exists('zend_shm_cache_store'))
3285
				zend_shm_cache_store('SMF::' . $key, $value, $ttl);
3286
			elseif (function_exists('output_cache_put'))
3287
				output_cache_put($key, $value);
3288
			break;
3289
		case 'xcache':
3290
			if (function_exists('xcache_set') && ini_get('xcache.var_size') > 0)
3291
			{
3292
				if ($value === null)
3293
					xcache_unset($key);
3294
				else
3295
					xcache_set($key, $value, $ttl);
3296
			}
3297
			break;
3298
		default:
3299
			// Otherwise custom cache?
3300
			if ($value === null)
3301
				@unlink($cachedir . '/data_' . $key . '.php');
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...
3302
			else
3303
			{
3304
				$cache_data = '<' . '?' . 'php if (!defined(\'SMF\')) die; if (' . (time() + $ttl) . ' < time()) $expired = true; else{$expired = false; $value = \'' . addcslashes($value, '\\\'') . '\';}' . '?' . '>';
3305
3306
				// Write out the cache file, check that the cache write was successful; all the data must be written
3307
				// If it fails due to low diskspace, or other, remove the cache file
3308
				if (file_put_contents($cachedir . '/data_' . $key . '.php', $cache_data, LOCK_EX) !== strlen($cache_data))
3309
					@unlink($cachedir . '/data_' . $key . '.php');
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...
3310
			}
3311
			break;
3312
	}
3313
3314
	if (function_exists('call_integration_hook'))
3315
		call_integration_hook('cache_put_data', array(&$key, &$value, &$ttl));
3316
3317 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3318
		$cache_hits[$cache_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
0 ignored issues
show
Bug introduced by
The variable $st 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...
3319
3320
	// Invalidate the opcode cache
3321
	if (function_exists('opcache_invalidate'))
3322
   		opcache_invalidate($cachedir . '/data_' . $key . '.php', true);
3323
3324
	if (function_exists('apc_delete_file'))
3325
   		@apc_delete_file($cachedir . '/data_' . $key . '.php');
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...
3326
}
3327
3328
/**
3329
 * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
3330
 * - It may often "miss", so shouldn't be depended on.
3331
 * - It supports the same as cache_put_data().
3332
 *
3333
 * @param string $key The key for the value to retrieve
3334
 * @param int $ttl The maximum age of the cached data
3335
 * @return string The cached data or null if nothing was loaded
3336
 */
3337
function cache_get_data($key, $ttl = 120)
3338
{
3339
	global $boardurl, $modSettings, $memcached;
3340
	global $cache_hits, $cache_count, $cache_misses, $cache_count_misses, $db_show_debug, $cachedir;
3341
	global $cache_accelerator, $cache_enable, $cache_memcached;
3342
3343
	if (empty($cache_enable))
3344
		return;
3345
3346
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3347
	if (isset($db_show_debug) && $db_show_debug === true)
3348
	{
3349
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'get');
3350
		$st = microtime();
3351
		$original_key = $key;
3352
	}
3353
3354
	$key = md5($boardurl . filemtime($cachedir . '/' . 'index.php')) . '-SMF-' . strtr($key, ':/', '-_');
3355
3356
	switch ($cache_accelerator)
3357
	{
3358
		case 'memcached':
3359
			// Okay, let's go for it memcached!
3360
			if ((function_exists('memcache_get') || function_exists('memcached_get')) && isset($cache_memcached) && trim($cache_memcached) != '')
3361
			{
3362
				// Not connected yet?
3363
				if (empty($memcached))
3364
					get_memcached_server();
3365
				if (!$memcached)
3366
				{
3367
					$cache_misses_count = isset($cache_misses) ? $cache_misses + 1 : 1;
0 ignored issues
show
Unused Code introduced by
$cache_misses_count is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3368
					$value = null;
3369
				}
3370
				else
3371
					$value = (function_exists('memcache_get')) ? memcache_get($memcached, $key) : memcached_get($memcached, $key);
3372
			}
3373
			break;
3374
		case 'apc':
3375
			// This is the free APC from PECL.
3376
			if (function_exists('apc_fetch'))
3377
				$value = apc_fetch($key . 'smf');
3378
			break;
3379 View Code Duplication
		case 'zend':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3380
			// Zend's pricey stuff.
3381
			if (function_exists('zend_shm_cache_fetch'))
3382
				$value = zend_shm_cache_fetch('SMF::' . $key);
3383
			elseif (function_exists('output_cache_get'))
3384
				$value = output_cache_get($key, $ttl);
3385
			break;
3386
		case 'xcache':
3387
			if (function_exists('xcache_get') && ini_get('xcache.var_size') > 0)
3388
				$value = xcache_get($key);
3389
			break;
3390
		default:
3391
			// Otherwise it's SMF data!
3392
			if (file_exists($cachedir . '/data_' . $key . '.php') && filesize($cachedir . '/data_' . $key . '.php') > 10)
3393
			{
3394
				// Work around Zend's opcode caching (PHP 5.5+), they would cache older files for a couple of seconds
3395
				// causing newer files to take effect a while later.
3396
				if (function_exists('opcache_invalidate'))
3397
					opcache_invalidate($cachedir . '/data_' . $key . '.php', true);
3398
3399
				// php will cache file_exists et all, we can't 100% depend on its results so proceed with caution
3400
				@include($cachedir . '/data_' . $key . '.php');
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...
3401
				if (!empty($expired) && isset($value))
0 ignored issues
show
Bug introduced by
The variable $expired seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
Bug introduced by
The variable $value seems only to be defined at a later point. As such the call to isset() seems to always evaluate to false.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
3402
				{
3403
					@unlink($cachedir . '/data_' . $key . '.php');
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...
3404
					unset($value);
3405
				}
3406
			}
3407
			break;
3408
	}
3409
3410
	if (isset($db_show_debug) && $db_show_debug === true)
3411
	{
3412
		$cache_hits[$cache_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
0 ignored issues
show
Bug introduced by
The variable $st 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...
3413
		$cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0;
3414
3415
		if (empty($value))
3416
		{
3417
			if (!isset($cache_misses))
3418
				$cache_misses = array();
3419
3420
			$cache_count_misses = isset($cache_count_misses) ? $cache_count_misses + 1 : 1;
3421
			$cache_misses[$cache_count_misses] = array('k' => $original_key, 'd' => 'get');
0 ignored issues
show
Bug introduced by
The variable $original_key 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...
3422
		}
3423
	}
3424
3425
	if (function_exists('call_integration_hook') && isset($value))
3426
		call_integration_hook('cache_get_data', array(&$key, &$ttl, &$value));
3427
3428
	return empty($value) ? null : smf_json_decode($value, true);
3429
}
3430
3431
/**
3432
 * Get memcache servers.
3433
 *
3434
 * - This function is used by cache_get_data() and cache_put_data().
3435
 * - It attempts to connect to a random server in the cache_memcached setting.
3436
 * - It recursively calls itself up to $level times.
3437
 *
3438
 * @param int $level The maximum number of times to call this function recursively
3439
 */
3440
function get_memcached_server($level = 3)
3441
{
3442
	global $memcached, $db_persist, $cache_memcached;
3443
3444
	$servers = explode(',', $cache_memcached);
3445
	$server = trim($servers[array_rand($servers)]);
3446
3447
	$port = 0;
3448
3449
	// Normal host names do not contain slashes, while e.g. unix sockets do. Assume alternative transport pipe with port 0.
3450
	if(strpos($server,'/') !== false)
3451
		$host = $server;
3452
	else
3453
	{
3454
		$server = explode(':', $server);
3455
		$host = $server[0];
3456
		$port = isset($server[1]) ? $server[1] : 11211;
3457
	}
3458
3459
	$cache = (function_exists('memcache_get')) ? 'memcache' : ((function_exists('memcached_get') ? 'memcached' : ''));
3460
3461
	// Don't try more times than we have servers!
3462
	$level = min(count($servers), $level);
3463
3464
	// Don't wait too long: yes, we want the server, but we might be able to run the query faster!
3465
	if (empty($db_persist))
3466
	{
3467
		if ($cache === 'memcached')
3468
			$memcached = memcached_connect($host, $port);
3469
		if ($cache === 'memcache')
3470
			$memcached = memcache_connect($host, $port);
3471
	}
3472
	else
3473
	{
3474
		if ($cache === 'memcached')
3475
			$memcached = memcached_pconnect($host, $port);
3476
		if ($cache === 'memcache')
3477
			$memcached = memcache_pconnect($host, $port);
3478
	}
3479
3480
	if (!$memcached && $level > 0)
3481
		get_memcached_server($level - 1);
3482
}
3483
3484
/**
3485
 * Helper function to set an array of data for an user's avatar.
3486
 *
3487
 * Makes assumptions based on the data provided, the following keys are required:
3488
 * - avatar The raw "avatar" column in members table
3489
 * - email The user's email. Used to get the gravatar info
3490
 * - filename The attachment filename
3491
 *
3492
 * @param array $data An array of raw info
3493
 * @return array An array of avatar data
3494
 */
3495
function set_avatar_data($data = array())
3496
{
3497
	global $modSettings, $boardurl, $smcFunc, $image_proxy_enabled, $image_proxy_secret;
3498
3499
	// Come on!
3500
	if (empty($data))
3501
		return array();
3502
3503
	// Set a nice default var.
3504
	$image = '';
3505
3506
	// Gravatar has been set as mandatory!
3507
	if (!empty($modSettings['gravatarOverride']))
3508
	{
3509
		if (!empty($modSettings['gravatarAllowExtraEmail']) && !empty($data['avatar']) && stristr($data['avatar'], 'gravatar://'))
3510
			$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3511
3512
		else if (!empty($data['email']))
3513
			$image = get_gravatar_url($data['email']);
3514
	}
3515
3516
	// Look if the user has a gravatar field or has set an external url as avatar.
3517
	else
3518
	{
3519
		// So it's stored in the member table?
3520
		if (!empty($data['avatar']))
3521
		{
3522
			// Gravatar.
3523
			if (stristr($data['avatar'], 'gravatar://'))
3524
			{
3525
				if ($data['avatar'] == 'gravatar://')
3526
					$image = get_gravatar_url($data['email']);
3527
3528
				elseif (!empty($modSettings['gravatarAllowExtraEmail']))
3529
					$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3530
			}
3531
3532
			// External url.
3533
			else
3534
			{
3535
				// Using ssl?
3536
				if (!empty($modSettings['force_ssl']) && $image_proxy_enabled && stripos($data['avatar'], 'http://') !== false)
3537
					$image = strtr($boardurl, array('http://' => 'https://')) . '/proxy.php?request=' . urlencode($data['avatar']) . '&hash=' . md5($data['avatar'] . $image_proxy_secret);
3538
3539
				// Just a plain external url.
3540
				else
3541
					$image = (stristr($data['avatar'], 'http://') || stristr($data['avatar'], 'https://')) ? $data['avatar'] : $modSettings['avatar_url'] . '/' . $data['avatar'];
3542
			}
3543
		}
3544
3545
		// Perhaps this user has an attachment as avatar...
3546
		else if (!empty($data['filename']))
3547
			$image = $modSettings['custom_avatar_url'] . '/' . $data['filename'];
3548
3549
		// Right... no avatar... use our default image.
3550
		else
3551
			$image = $modSettings['avatar_url'] . '/default.png';
3552
	}
3553
3554
	call_integration_hook('integrate_set_avatar_data', array(&$image, &$data));
3555
3556
	// At this point in time $image has to be filled unless you chose to force gravatar and the user doesn't have the needed data to retrieve it... thus a check for !empty() is still needed.
3557
	if (!empty($image))
3558
		return array(
3559
			'name' => !empty($data['avatar']) ? $data['avatar'] : '',
3560
			'image' => '<img class="avatar" src="' . $image . '" />',
3561
			'href' => $image,
3562
			'url' => $image,
3563
		);
3564
3565
	// Fallback to make life easier for everyone...
3566
	else
3567
		return array(
3568
			'name' => '',
3569
			'image' => '',
3570
			'href' => '',
3571
			'url' => '',
3572
		);
3573
}
3574
3575
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...