Completed
Push — release-2.1 ( aa21c4...7040ad )
by Mathias
09:20
created

Load.php ➔ getBoardParents()   C

Complexity

Conditions 10
Paths 2

Size

Total Lines 78
Code Lines 39

Duplication

Lines 20
Ratio 25.64 %

Importance

Changes 0
Metric Value
cc 10
eloc 39
nc 2
nop 1
dl 20
loc 78
rs 5.6321
c 0
b 0
f 0

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 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
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']('', '
30
			SET NAMES {string:db_character_set}',
31
			array(
32
				'db_character_set' => $db_character_set,
33
			)
34
		);
35
36
	// We need some caching support, maybe.
37
	loadCacheAccelerator();
38
39
	// Try to load it from the cache first; it'll never get cached if the setting is off.
40
	if (($modSettings = cache_get_data('modSettings', 90)) == null)
41
	{
42
		$request = $smcFunc['db_query']('', '
43
			SELECT variable, value
44
			FROM {db_prefix}settings',
45
			array(
46
			)
47
		);
48
		$modSettings = array();
49
		if (!$request)
50
			display_db_error();
51 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...
52
			$modSettings[$row[0]] = $row[1];
53
		$smcFunc['db_free_result']($request);
54
55
		// Do a few things to protect against missing settings or settings with invalid values...
56 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...
57
			$modSettings['defaultMaxTopics'] = 20;
58 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...
59
			$modSettings['defaultMaxMessages'] = 15;
60 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...
61
			$modSettings['defaultMaxMembers'] = 30;
62 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...
63
			$modSettings['defaultMaxListItems'] = 15;
64
65
		if (!is_array($modSettings['attachmentUploadDir']))
66
			$modSettings['attachmentUploadDir'] = smf_json_decode($modSettings['attachmentUploadDir'], true);
67
68
		if (!empty($cache_enable))
69
			cache_put_data('modSettings', $modSettings, 90);
70
	}
71
72
	$modSettings['cache_enable'] = $cache_enable;
73
74
	// UTF-8 ?
75
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
76
77
	// Set a list of common functions.
78
	$ent_list = empty($modSettings['disableEntityCheck']) ? '&(#\d{1,7}|quot|amp|lt|gt|nbsp);' : '&(#021|quot|amp|lt|gt|nbsp);';
79
	$ent_check = empty($modSettings['disableEntityCheck']) ? function($string)
80
		{
81
			$string = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $string);
82
			return $string;
83
		} : function($string)
84
		{
85
			return $string;
86
		};
87
	$fix_utf8mb4 = function($string) use ($utf8, $smcFunc)
88
	{
89
		if (!$utf8 || $smcFunc['db_mb4'])
90
			return $string;
91
92
		$i = 0;
93
		$len = strlen($string);
94
		$new_string = '';
95
		while ($i < $len)
96
		{
97
			$ord = ord($string[$i]);
98
			if ($ord < 128)
99
			{
100
				$new_string .= $string[$i];
101
				$i++;
102
			}
103 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...
104
			{
105
				$new_string .= $string[$i] . $string[$i + 1];
106
				$i += 2;
107
			}
108 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...
109
			{
110
				$new_string .= $string[$i] . $string[$i + 1] . $string[$i + 2];
111
				$i += 3;
112
			}
113
			elseif ($ord < 248)
114
			{
115
				// Magic happens.
116
				$val = (ord($string[$i]) & 0x07) << 18;
117
				$val += (ord($string[$i + 1]) & 0x3F) << 12;
118
				$val += (ord($string[$i + 2]) & 0x3F) << 6;
119
				$val += (ord($string[$i + 3]) & 0x3F);
120
				$new_string .= '&#' . $val . ';';
121
				$i += 4;
122
			}
123
		}
124
		return $new_string;
125
	};
126
127
	// Preg_replace space characters depend on the character set in use
128
	$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';
129
130
	// global array of anonymous helper functions, used mostly to properly handle multi byte strings
131
	$smcFunc += array(
132
		'entity_fix' => function($string)
133
		{
134
			$num = $string[0] === 'x' ? hexdec(substr($string, 1)) : (int) $string;
135
			return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? '' : '&#' . $num . ';';
136
		},
137
		'htmlspecialchars' => function($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') use ($ent_check, $utf8, $fix_utf8mb4)
138
		{
139
			return $fix_utf8mb4($ent_check(htmlspecialchars($string, $quote_style, $utf8 ? 'UTF-8' : $charset)));
140
		},
141
		'htmltrim' => function($string) use ($utf8, $space_chars, $ent_check)
142
		{
143
			return preg_replace('~^(?:[ \t\n\r\x0B\x00' . $space_chars . ']|&nbsp;)+|(?:[ \t\n\r\x0B\x00' . $space_chars . ']|&nbsp;)+$~' . ($utf8 ? 'u' : ''), '', $ent_check($string));
144
		},
145
		'strlen' => function($string) use ($ent_list, $utf8, $ent_check)
146
		{
147
			return strlen(preg_replace('~' . $ent_list . ($utf8 ? '|.~u' : '~'), '_', $ent_check($string)));
148
		},
149
		'strpos' => function($haystack, $needle, $offset = 0) use ($utf8, $ent_check, $modSettings)
150
		{
151
			$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);
152
153
			if (strlen($needle) === 1)
154
			{
155
				$result = array_search($needle, array_slice($haystack_arr, $offset));
156
				return is_int($result) ? $result + $offset : false;
157
			}
158
			else
159
			{
160
				$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);
161
				$needle_size = count($needle_arr);
162
163
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
164
				while ((int) $result === $result)
165
				{
166
					$offset += $result;
167
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
168
						return $offset;
169
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
170
				}
171
				return false;
172
			}
173
		},
174
		'substr' => function($string, $start, $length = null) use ($utf8, $ent_check, $modSettings)
175
		{
176
			$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);
177
			return $length === null ? implode('', array_slice($ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
178
		},
179 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...
180
		{
181
			if (!function_exists('mb_strtolower'))
182
			{
183
				require_once($sourcedir . '/Subs-Charset.php');
184
				return utf8_strtolower($string);
185
			}
186
187
			return mb_strtolower($string, 'UTF-8');
188
		} : 'strtolower',
189
		'strtoupper' => $utf8 ? function($string)
190
		{
191
			global $sourcedir;
192
193
			if (!function_exists('mb_strtolower'))
194
			{
195
				require_once($sourcedir . '/Subs-Charset.php');
196
				return utf8_strtoupper($string);
197
			}
198
199
			return mb_strtoupper($string, 'UTF-8');
200
		} : 'strtoupper',
201
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
202
		{
203
			$string = $ent_check($string);
204
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~' . ($utf8 ? 'u' : ''), $string, $matches);
205
			$string = $matches[0];
206
			while (strlen($string) > $length)
207
				$string = preg_replace('~(?:' . $ent_list . '|.)$~' . ($utf8 ? 'u' : ''), '', $string);
208
			return $string;
209
		},
210
		'ucfirst' => $utf8 ? function($string) use (&$smcFunc)
211
		{
212
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
213
		} : 'ucfirst',
214
		'ucwords' => $utf8 ? function($string) use (&$smcFunc)
215
		{
216
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
217
			for ($i = 0, $n = count($words); $i < $n; $i += 2)
218
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
219
			return implode('', $words);
220
		} : 'ucwords',
221
	);
222
223
	// Setting the timezone is a requirement for some functions.
224
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], timezone_identifiers_list()))
225
		date_default_timezone_set($modSettings['default_timezone']);
226
	else
227
	{
228
		// Get PHP's default timezone, if set
229
		$ini_tz = ini_get('date.timezone');
230
		if (!empty($ini_tz))
231
			$modSettings['default_timezone'] = $ini_tz;
232
		else
233
			$modSettings['default_timezone'] = '';
234
235
		// If date.timezone is unset, invalid, or just plain weird, make a best guess
236 View Code Duplication
		if (!in_array($modSettings['default_timezone'], timezone_identifiers_list()))
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...
237
		{
238
			$server_offset = @mktime(0, 0, 0, 1, 1, 1970);
239
			$modSettings['default_timezone'] = timezone_name_from_abbr('', $server_offset, 0);
240
		}
241
242
		date_default_timezone_set($modSettings['default_timezone']);
243
	}
244
245
	// Check the load averages?
246
	if (!empty($modSettings['loadavg_enable']))
247
	{
248
		if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null)
249
		{
250
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
251
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
252
				$modSettings['load_average'] = (float) $matches[1];
253 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...
254
				$modSettings['load_average'] = (float) $matches[1];
255
			else
256
				unset($modSettings['load_average']);
257
258
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
259
				cache_put_data('loadavg', $modSettings['load_average'], 90);
260
		}
261
262
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
263
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
264
265
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
266
			display_loadavg_error();
267
	}
268
269
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
270
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
271
272
	// Here to justify the name of this function. :P
273
	// It should be added to the install and upgrade scripts.
274
	// But since the converters need to be updated also. This is easier.
275
	if (empty($modSettings['currentAttachmentUploadDir']))
276
	{
277
		updateSettings(array(
278
			'attachmentUploadDir' => json_encode(array(1 => $modSettings['attachmentUploadDir'])),
279
			'currentAttachmentUploadDir' => 1,
280
		));
281
	}
282
283
	// Integration is cool.
284
	if (defined('SMF_INTEGRATION_SETTINGS'))
285
	{
286
		$integration_settings = smf_json_decode(SMF_INTEGRATION_SETTINGS, true);
287
		foreach ($integration_settings as $hook => $function)
288
			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...
289
	}
290
291
	// Any files to pre include?
292 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...
293
	{
294
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
295
		foreach ($pre_includes as $include)
296
		{
297
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
298
			if (file_exists($include))
299
				require_once($include);
300
		}
301
	}
302
303
	// This determines the server... not used in many places, except for login fixing.
304
	$context['server'] = array(
305
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
306
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
307
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
308
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
309
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
310
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
311
		'is_windows' => strpos(PHP_OS, 'WIN') === 0,
312
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
313
	);
314
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
315
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
316
317
	// Define a list of icons used across multiple places.
318
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
319
320
	// Define an array for custom profile fields placements.
321
	$context['cust_profile_fields_placement'] = array(
322
		'standard',
323
		'icons',
324
		'above_signature',
325
		'below_signature',
326
		'below_avatar',
327
		'above_member',
328
		'bottom_poster',
329
	);
330
331
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
332
	$context['meta_tags'] = array();
333
334
	// Define an array of allowed HTML tags.
335
	$context['allowed_html_tags'] = array(
336
		'<img>',
337
		'<div>',
338
	);
339
340
	// These are the only valid image types for SMF, by default anyway.
341
	$context['validImageTypes'] = array(
342
		1 => 'gif',
343
		2 => 'jpeg',
344
		3 => 'png',
345
		5 => 'psd',
346
		6 => 'bmp',
347
		7 => 'tiff',
348
		8 => 'tiff',
349
		9 => 'jpeg',
350
		14 => 'iff'
351
	);
352
353
	// Define a list of allowed tags for descriptions.
354
	$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',);
355
356
	// Get an error count, if necessary
357
	if (!isset($context['num_errors']))
358
	{
359
		$query = $smcFunc['db_query']('', '
360
			SELECT COUNT(id_error)
361
			FROM {db_prefix}log_errors',
362
			array()
363
		);
364
365
		list($context['num_errors']) = $smcFunc['db_fetch_row']($query);
366
		$smcFunc['db_free_result']($query);
367
	}
368
369
	// Call pre load integration functions.
370
	call_integration_hook('integrate_pre_load');
371
}
372
373
/**
374
 * Load all the important user information.
375
 * What it does:
376
 * 	- sets up the $user_info array
377
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
378
 * 	- first checks for cookie or integration validation.
379
 * 	- uses the current session if no integration function or cookie is found.
380
 * 	- checks password length, if member is activated and the login span isn't over.
381
 * 		- if validation fails for the user, $id_member is set to 0.
382
 * 		- updates the last visit time when needed.
383
 */
384
function loadUserSettings()
385
{
386
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
387
	global $cookiename, $user_info, $language, $context, $image_proxy_enabled, $image_proxy_secret, $boardurl;
388
389
	// Check first the integration, then the cookie, and last the session.
390
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
391
	{
392
		$id_member = 0;
393
		foreach ($integration_ids as $integration_id)
394
		{
395
			$integration_id = (int) $integration_id;
396
			if ($integration_id > 0)
397
			{
398
				$id_member = $integration_id;
399
				$already_verified = true;
400
				break;
401
			}
402
		}
403
	}
404
	else
405
		$id_member = 0;
406
407
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
408
	{
409
		$cookie_data = smf_json_decode($_COOKIE[$cookiename], true, false);
410
411
		if (empty($cookie_data))
412
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
413
414
		list ($id_member, $password) = $cookie_data;
415
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
416
	}
417
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
418
	{
419
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
420
		$cookie_data = smf_json_decode($_SESSION['login_' . $cookiename]);
421
422
		if (empty($cookie_data))
423
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
424
425
		list ($id_member, $password, $login_span) = $cookie_data;
426
		$id_member = !empty($id_member) && strlen($password) == 128 && $login_span > time() ? (int) $id_member : 0;
427
	}
428
429
	// Only load this stuff if the user isn't a guest.
430
	if ($id_member != 0)
431
	{
432
		// Is the member data cached?
433
		if (empty($modSettings['cache_enable']) || $modSettings['cache_enable'] < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
434
		{
435
			$request = $smcFunc['db_query']('', '
436
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
437
				FROM {db_prefix}members AS mem
438
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
439
				WHERE mem.id_member = {int:id_member}
440
				LIMIT 1',
441
				array(
442
					'id_member' => $id_member,
443
				)
444
			);
445
			$user_settings = $smcFunc['db_fetch_assoc']($request);
446
			$smcFunc['db_free_result']($request);
447
448 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...
449
				$user_settings['avatar'] = strtr($boardurl, array('http://' => 'https://')) . '/proxy.php?request=' . urlencode($user_settings['avatar']) . '&hash=' . md5($user_settings['avatar'] . $image_proxy_secret);
450
451
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
452
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
453
		}
454
455
		// Did we find 'im?  If not, junk it.
456
		if (!empty($user_settings))
457
		{
458
			// As much as the password should be right, we can assume the integration set things up.
459
			if (!empty($already_verified) && $already_verified === true)
460
				$check = true;
461
			// SHA-512 hash should be 128 characters long.
462
			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...
463
				$check = hash_salt($user_settings['passwd'], $user_settings['password_salt']) == $password;
464
			else
465
				$check = false;
466
467
			// Wrong password or not activated - either way, you're going nowhere.
468
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
469
		}
470
		else
471
			$id_member = 0;
472
473
		// If we no longer have the member maybe they're being all hackey, stop brute force!
474
		if (!$id_member)
475
		{
476
			require_once($sourcedir . '/LogInOut.php');
477
			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);
478
		}
479
		// Validate for Two Factor Authentication
480
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
481
		{
482
			$tfacookie = $cookiename . '_tfa';
483
			$tfasecret = null;
484
485
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
486
487
			if (empty($verified) || !in_array(true, $verified))
488
			{
489
				if (!empty($_COOKIE[$tfacookie]))
490
				{
491
					$tfa_data = smf_json_decode($_COOKIE[$tfacookie]);
492
493
					if (is_null($tfa_data))
494
						$tfa_data = safe_unserialize($_COOKIE[$tfacookie]);
495
496
					list ($tfamember, $tfasecret) = $tfa_data;
497
498
					if ((int) $tfamember != $id_member)
499
						$tfasecret = null;
500
				}
501
502
				if (empty($tfasecret) || hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']) != $tfasecret)
503
				{
504
					$id_member = 0;
505
					redirectexit('action=logintfa');
506
				}
507
			}
508
		}
509
		// When authenticating their two factor code, make sure to reset their ID for security
510
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
511
		{
512
			$id_member = 0;
513
			$context['tfa_member'] = $user_settings;
514
			$user_settings = array();
515
		}
516
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
517
		elseif (!empty($modSettings['tfa_mode']) && $modSettings['tfa_mode'] >= 2 && $id_member && empty($user_settings['tfa_secret']))
518
		{
519
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
520
			{
521
				//Build an array of ALL user membergroups.
522
				$full_groups = array($user_settings['id_group']);
523
				if (!empty($user_settings['additional_groups']))
524
				{
525
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
526
					$full_groups = array_unique($full_groups); //duplicates, maybe?
527
				}
528
529
				//Find out if any group requires 2FA
530
				$request = $smcFunc['db_query']('', '
531
					SELECT COUNT(id_group) AS total
532
					FROM {db_prefix}membergroups
533
					WHERE tfa_required = {int:tfa_required}
534
						AND id_group IN ({array_int:full_groups})',
535
					array(
536
						'tfa_required' => 1,
537
						'full_groups' => $full_groups,
538
					)
539
				);
540
				$row = $smcFunc['db_fetch_assoc']($request);
541
				$smcFunc['db_free_result']($request);
542
			}
543
			else
544
				$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...
545
546
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
547
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
548
549
			if ($row['total'] > 0 && !in_array($action, array('profile', 'logout')) || ($action == 'profile' && $area != 'tfasetup'))
550
				redirectexit('action=profile;area=tfasetup;forced');
551
		}
552
	}
553
554
	// Found 'im, let's set up the variables.
555
	if ($id_member != 0)
556
	{
557
		// Let's not update the last visit time in these cases...
558
		// 1. SSI doesn't count as visiting the forum.
559
		// 2. RSS feeds and XMLHTTP requests don't count either.
560
		// 3. If it was set within this session, no need to set it again.
561
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
562
		// 5. We're still logging in or authenticating
563
		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))
564
		{
565
			// @todo can this be cached?
566
			// Do a quick query to make sure this isn't a mistake.
567
			$result = $smcFunc['db_query']('', '
568
				SELECT poster_time
569
				FROM {db_prefix}messages
570
				WHERE id_msg = {int:id_msg}
571
				LIMIT 1',
572
				array(
573
					'id_msg' => $user_settings['id_msg_last_visit'],
574
				)
575
			);
576
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
577
			$smcFunc['db_free_result']($result);
578
579
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
580
581
			// If it was *at least* five hours ago...
582
			if ($visitTime < time() - 5 * 3600)
583
			{
584
				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']));
585
				$user_settings['last_login'] = time();
586
587
				if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
588
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
589
590
				if (!empty($modSettings['cache_enable']))
591
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
592
			}
593
		}
594
		elseif (empty($_SESSION['id_msg_last_visit']))
595
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
596
597
		$username = $user_settings['member_name'];
598
599
		if (empty($user_settings['additional_groups']))
600
			$user_info = array(
601
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
602
			);
603 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...
604
			$user_info = array(
605
				'groups' => array_merge(
606
					array($user_settings['id_group'], $user_settings['id_post_group']),
607
					explode(',', $user_settings['additional_groups'])
608
				)
609
			);
610
611
		// Because history has proven that it is possible for groups to go bad - clean up in case.
612
		foreach ($user_info['groups'] as $k => $v)
613
			$user_info['groups'][$k] = (int) $v;
614
615
		// This is a logged in user, so definitely not a spider.
616
		$user_info['possibly_robot'] = false;
617
618
		// Figure out the new time offset.
619
		if (!empty($user_settings['timezone']))
620
		{
621
			// Get the offsets from UTC for the server, then for the user.
622
			$tz_system = new DateTimeZone(@date_default_timezone_get());
623
			$tz_user = new DateTimeZone($user_settings['timezone']);
624
			$time_system = new DateTime('now', $tz_system);
625
			$time_user = new DateTime('now', $tz_user);
626
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
627
		}
628
		else
629
		{
630
			// !!! Compatibility.
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
631
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'];
632
		}
633
	}
634
	// If the user is a guest, initialize all the critical user settings.
635
	else
636
	{
637
		// This is what a guest's variables should be.
638
		$username = '';
639
		$user_info = array('groups' => array(-1));
640
		$user_settings = array();
641
642
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
643
			$_COOKIE[$cookiename] = '';
644
645
		// Expire the 2FA cookie
646
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
647
		{
648
			$tfa_data = smf_json_decode($_COOKIE[$cookiename . '_tfa'], true);
649
650
			if (is_null($tfa_data))
651
				$tfa_data = safe_unserialize($_COOKIE[$cookiename . '_tfa']);
652
653
			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...
654
655
			if (!$preserve || time() > $exp)
656
			{
657
				$_COOKIE[$cookiename . '_tfa'] = '';
658
				setTFACookie(-3600, 0, '');
659
			}
660
		}
661
662
		// Create a login token if it doesn't exist yet.
663
		if (!isset($_SESSION['token']['post-login']))
664
			createToken('login');
665
		else
666
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
667
668
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
669
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
670
		{
671
			require_once($sourcedir . '/ManageSearchEngines.php');
672
			$user_info['possibly_robot'] = SpiderCheck();
673
		}
674
		elseif (!empty($modSettings['spider_mode']))
675
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
676
		// If we haven't turned on proper spider hunts then have a guess!
677
		else
678
		{
679
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
680
			$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;
681
		}
682
683
		// We don't know the offset...
684
		$user_info['time_offset'] = 0;
685
	}
686
687
	// Set up the $user_info array.
688
	$user_info += array(
689
		'id' => $id_member,
690
		'username' => $username,
691
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
692
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
693
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
694
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
695
		'is_guest' => $id_member == 0,
696
		'is_admin' => in_array(1, $user_info['groups']),
697
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
698
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
699
		'ip' => $_SERVER['REMOTE_ADDR'],
700
		'ip2' => $_SERVER['BAN_CHECK_IP'],
701
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
702
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
703
		'avatar' => array(
704
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
705
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
706
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
707
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
708
		),
709
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
710
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
711
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
712
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
713
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
714
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
715
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
716
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
717
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
718
		'permissions' => array(),
719
	);
720
	$user_info['groups'] = array_unique($user_info['groups']);
721
722
	// 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.
723
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
724
		unset($user_info['ignoreboards'][$tmp]);
725
726
	// Allow the user to change their language.
727
	if (!empty($modSettings['userLanguage']))
728
	{
729
		$languages = getLanguages();
730
731
		// Is it valid?
732
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
733
		{
734
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
735
736
			// Make it permanent for members.
737
			if (!empty($user_info['id']))
738
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
739
			else
740
				$_SESSION['language'] = $user_info['language'];
741
		}
742
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
743
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
744
	}
745
746
	// Just build this here, it makes it easier to change/use - administrators can see all boards.
747
	if ($user_info['is_admin'])
748
		$user_info['query_see_board'] = '1=1';
749
	// Otherwise just the groups in $user_info['groups'].
750
	else
751
		$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'] : '') . ')';
752
753
	// Build the list of boards they WANT to see.
754
	// This will take the place of query_see_boards in certain spots, so it better include the boards they can see also
755
756
	// If they aren't ignoring any boards then they want to see all the boards they can see
757
	if (empty($user_info['ignoreboards']))
758
		$user_info['query_wanna_see_board'] = $user_info['query_see_board'];
759
	// Ok I guess they don't want to see all the boards
760
	else
761
		$user_info['query_wanna_see_board'] = '(' . $user_info['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $user_info['ignoreboards']) . '))';
762
763
	call_integration_hook('integrate_user_info');
764
}
765
766
/**
767
 * Check for moderators and see if they have access to the board.
768
 * What it does:
769
 * - sets up the $board_info array for current board information.
770
 * - if cache is enabled, the $board_info array is stored in cache.
771
 * - redirects to appropriate post if only message id is requested.
772
 * - is only used when inside a topic or board.
773
 * - determines the local moderators for the board.
774
 * - adds group id 3 if the user is a local moderator for the board they are in.
775
 * - prevents access if user is not in proper group nor a local moderator of the board.
776
 */
777
function loadBoard()
778
{
779
	global $txt, $scripturl, $context, $modSettings;
780
	global $board_info, $board, $topic, $user_info, $smcFunc;
781
782
	// Assume they are not a moderator.
783
	$user_info['is_mod'] = false;
784
	$context['user']['is_mod'] = &$user_info['is_mod'];
785
786
	// Start the linktree off empty..
787
	$context['linktree'] = array();
788
789
	// Have they by chance specified a message id but nothing else?
790
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
791
	{
792
		// Make sure the message id is really an int.
793
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
794
795
		// Looking through the message table can be slow, so try using the cache first.
796
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
797
		{
798
			$request = $smcFunc['db_query']('', '
799
				SELECT id_topic
800
				FROM {db_prefix}messages
801
				WHERE id_msg = {int:id_msg}
802
				LIMIT 1',
803
				array(
804
					'id_msg' => $_REQUEST['msg'],
805
				)
806
			);
807
808
			// So did it find anything?
809
			if ($smcFunc['db_num_rows']($request))
810
			{
811
				list ($topic) = $smcFunc['db_fetch_row']($request);
812
				$smcFunc['db_free_result']($request);
813
				// Save save save.
814
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
815
			}
816
		}
817
818
		// Remember redirection is the key to avoiding fallout from your bosses.
819
		if (!empty($topic))
820
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
821
		else
822
		{
823
			loadPermissions();
824
			loadTheme();
825
			fatal_lang_error('topic_gone', false);
826
		}
827
	}
828
829
	// Load this board only if it is specified.
830
	if (empty($board) && empty($topic))
831
	{
832
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
833
		return;
834
	}
835
836
	if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3))
837
	{
838
		// @todo SLOW?
839
		if (!empty($topic))
840
			$temp = cache_get_data('topic_board-' . $topic, 120);
841
		else
842
			$temp = cache_get_data('board-' . $board, 120);
843
844
		if (!empty($temp))
845
		{
846
			$board_info = $temp;
847
			$board = $board_info['id'];
848
		}
849
	}
850
851
	if (empty($temp))
852
	{
853
		$request = $smcFunc['db_query']('load_board_info', '
854
			SELECT
855
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
856
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
857
				COALESCE(mem.id_member, 0) AS id_moderator,
858
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
859
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
860
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
861
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
862
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
863
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
864
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
865
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
866
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
867
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
868
			WHERE b.id_board = {raw:board_link}',
869
			array(
870
				'current_topic' => $topic,
871
				'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
872
			)
873
		);
874
		// If there aren't any, skip.
875
		if ($smcFunc['db_num_rows']($request) > 0)
876
		{
877
			$row = $smcFunc['db_fetch_assoc']($request);
878
879
			// Set the current board.
880
			if (!empty($row['id_board']))
881
				$board = $row['id_board'];
882
883
			// Basic operating information. (globals... :/)
884
			$board_info = array(
885
				'id' => $board,
886
				'moderators' => array(),
887
				'moderator_groups' => array(),
888
				'cat' => array(
889
					'id' => $row['id_cat'],
890
					'name' => $row['cname']
891
				),
892
				'name' => $row['bname'],
893
				'description' => $row['description'],
894
				'num_topics' => $row['num_topics'],
895
				'unapproved_topics' => $row['unapproved_topics'],
896
				'unapproved_posts' => $row['unapproved_posts'],
897
				'unapproved_user_topics' => 0,
898
				'parent_boards' => getBoardParents($row['id_parent']),
899
				'parent' => $row['id_parent'],
900
				'child_level' => $row['child_level'],
901
				'theme' => $row['id_theme'],
902
				'override_theme' => !empty($row['override_theme']),
903
				'profile' => $row['id_profile'],
904
				'redirect' => $row['redirect'],
905
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
906
				'posts_count' => empty($row['count_posts']),
907
				'cur_topic_approved' => empty($topic) || $row['approved'],
908
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
909
			);
910
911
			// Load the membergroups allowed, and check permissions.
912
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
913
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
914
915
			do
916
			{
917 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...
918
					$board_info['moderators'][$row['id_moderator']] = array(
919
						'id' => $row['id_moderator'],
920
						'name' => $row['real_name'],
921
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
922
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
923
					);
924
925 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...
926
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
927
						'id' => $row['id_moderator_group'],
928
						'name' => $row['group_name'],
929
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
930
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
931
					);
932
			}
933
			while ($row = $smcFunc['db_fetch_assoc']($request));
934
935
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
936
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
937
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
938
			{
939
				// Free the previous result
940
				$smcFunc['db_free_result']($request);
941
942
				// @todo why is this using id_topic?
943
				// @todo Can this get cached?
944
				$request = $smcFunc['db_query']('', '
945
					SELECT COUNT(id_topic)
946
					FROM {db_prefix}topics
947
					WHERE id_member_started={int:id_member}
948
						AND approved = {int:unapproved}
949
						AND id_board = {int:board}',
950
					array(
951
						'id_member' => $user_info['id'],
952
						'unapproved' => 0,
953
						'board' => $board,
954
					)
955
				);
956
957
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
958
			}
959
960
			if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3))
961
			{
962
				// @todo SLOW?
963
				if (!empty($topic))
964
					cache_put_data('topic_board-' . $topic, $board_info, 120);
965
				cache_put_data('board-' . $board, $board_info, 120);
966
			}
967
		}
968
		else
969
		{
970
			// Otherwise the topic is invalid, there are no moderators, etc.
971
			$board_info = array(
972
				'moderators' => array(),
973
				'moderator_groups' => array(),
974
				'error' => 'exist'
975
			);
976
			$topic = null;
977
			$board = 0;
978
		}
979
		$smcFunc['db_free_result']($request);
980
	}
981
982
	if (!empty($topic))
983
		$_GET['board'] = (int) $board;
984
985
	if (!empty($board))
986
	{
987
		// Get this into an array of keys for array_intersect
988
		$moderator_groups = array_keys($board_info['moderator_groups']);
989
990
		// Now check if the user is a moderator.
991
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
992
993 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...
994
			$board_info['error'] = 'access';
995 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...
996
			$board_info['error'] = 'access';
997
998
		// Build up the linktree.
999
		$context['linktree'] = array_merge(
1000
			$context['linktree'],
1001
			array(array(
1002
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
1003
				'name' => $board_info['cat']['name']
1004
			)),
1005
			array_reverse($board_info['parent_boards']),
1006
			array(array(
1007
				'url' => $scripturl . '?board=' . $board . '.0',
1008
				'name' => $board_info['name']
1009
			))
1010
		);
1011
	}
1012
1013
	// Set the template contextual information.
1014
	$context['user']['is_mod'] = &$user_info['is_mod'];
1015
	$context['current_topic'] = $topic;
1016
	$context['current_board'] = $board;
1017
1018
	// No posting in redirection boards!
1019
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1020
		$board_info['error'] == 'post_in_redirect';
1021
1022
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1023
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1024
	{
1025
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1026
		loadPermissions();
1027
		loadTheme();
1028
1029
		$_GET['board'] = '';
1030
		$_GET['topic'] = '';
1031
1032
		// The linktree should not give the game away mate!
1033
		$context['linktree'] = array(
1034
			array(
1035
				'url' => $scripturl,
1036
				'name' => $context['forum_name_html_safe']
1037
			)
1038
		);
1039
1040
		// If it's a prefetching agent or we're requesting an attachment.
1041
		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...
1042
		{
1043
			ob_end_clean();
1044
			header('HTTP/1.1 403 Forbidden');
1045
			die;
1046
		}
1047
		elseif ($board_info['error'] == 'post_in_redirect')
1048
		{
1049
			// Slightly different error message here...
1050
			fatal_lang_error('cannot_post_redirect', false);
1051
		}
1052
		elseif ($user_info['is_guest'])
1053
		{
1054
			loadLanguage('Errors');
1055
			is_not_guest($txt['topic_gone']);
1056
		}
1057
		else
1058
			fatal_lang_error('topic_gone', false);
1059
	}
1060
1061
	if ($user_info['is_mod'])
1062
		$user_info['groups'][] = 3;
1063
}
1064
1065
/**
1066
 * Load this user's permissions.
1067
 */
1068
function loadPermissions()
1069
{
1070
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir;
1071
1072
	if ($user_info['is_admin'])
1073
	{
1074
		banPermissions();
1075
		return;
1076
	}
1077
1078
	if (!empty($modSettings['cache_enable']))
1079
	{
1080
		$cache_groups = $user_info['groups'];
1081
		asort($cache_groups);
1082
		$cache_groups = implode(',', $cache_groups);
1083
		// If it's a spider then cache it different.
1084
		if ($user_info['possibly_robot'])
1085
			$cache_groups .= '-spider';
1086
1087
		if ($modSettings['cache_enable'] >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1088
		{
1089
			list ($user_info['permissions']) = $temp;
1090
			banPermissions();
1091
1092
			return;
1093
		}
1094
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1095
			list ($user_info['permissions'], $removals) = $temp;
1096
	}
1097
1098
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1099
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1100
1101
	if (empty($user_info['permissions']))
1102
	{
1103
		// Get the general permissions.
1104
		$request = $smcFunc['db_query']('', '
1105
			SELECT permission, add_deny
1106
			FROM {db_prefix}permissions
1107
			WHERE id_group IN ({array_int:member_groups})
1108
				' . $spider_restrict,
1109
			array(
1110
				'member_groups' => $user_info['groups'],
1111
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1112
			)
1113
		);
1114
		$removals = array();
1115 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...
1116
		{
1117
			if (empty($row['add_deny']))
1118
				$removals[] = $row['permission'];
1119
			else
1120
				$user_info['permissions'][] = $row['permission'];
1121
		}
1122
		$smcFunc['db_free_result']($request);
1123
1124
		if (isset($cache_groups))
1125
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1126
	}
1127
1128
	// Get the board permissions.
1129
	if (!empty($board))
1130
	{
1131
		// Make sure the board (if any) has been loaded by loadBoard().
1132
		if (!isset($board_info['profile']))
1133
			fatal_lang_error('no_board');
1134
1135
		$request = $smcFunc['db_query']('', '
1136
			SELECT permission, add_deny
1137
			FROM {db_prefix}board_permissions
1138
			WHERE (id_group IN ({array_int:member_groups})
1139
				' . $spider_restrict . ')
1140
				AND id_profile = {int:id_profile}',
1141
			array(
1142
				'member_groups' => $user_info['groups'],
1143
				'id_profile' => $board_info['profile'],
1144
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1145
			)
1146
		);
1147 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...
1148
		{
1149
			if (empty($row['add_deny']))
1150
				$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...
1151
			else
1152
				$user_info['permissions'][] = $row['permission'];
1153
		}
1154
		$smcFunc['db_free_result']($request);
1155
	}
1156
1157
	// Remove all the permissions they shouldn't have ;).
1158
	if (!empty($modSettings['permission_enable_deny']))
1159
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
1160
1161
	if (isset($cache_groups) && !empty($board) && $modSettings['cache_enable'] >= 2)
1162
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1163
1164
	// Banned?  Watch, don't touch..
1165
	banPermissions();
1166
1167
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1168
	if (!$user_info['is_guest'])
1169
	{
1170
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1171
		{
1172
			require_once($sourcedir . '/Subs-Auth.php');
1173
			rebuildModCache();
1174
		}
1175
		else
1176
			$user_info['mod_cache'] = $_SESSION['mc'];
1177
1178
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1179
		// For example this drastically simplifies certain changes to the profile area.
1180
		$user_info['permissions'][] = 'is_not_guest';
1181
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1182
		$user_info['permissions'][] = 'profile_view_own';
1183
		if (in_array('profile_view', $user_info['permissions']))
1184
			$user_info['permissions'][] = 'profile_view_any';
1185
	}
1186
}
1187
1188
/**
1189
 * Loads an array of users' data by ID or member_name.
1190
 *
1191
 * @param array|string $users An array of users by id or name or a single username/id
1192
 * @param bool $is_name Whether $users contains names
1193
 * @param string $set What kind of data to load (normal, profile, minimal)
1194
 * @return array The ids of the members loaded
1195
 */
1196
function loadMemberData($users, $is_name = false, $set = 'normal')
1197
{
1198
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1199
	global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1200
1201
	// Can't just look for no users :P.
1202
	if (empty($users))
1203
		return array();
1204
1205
	// Pass the set value
1206
	$context['loadMemberContext_set'] = $set;
1207
1208
	// Make sure it's an array.
1209
	$users = !is_array($users) ? array($users) : array_unique($users);
1210
	$loaded_ids = array();
1211
1212
	if (!$is_name && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1213
	{
1214
		$users = array_values($users);
1215
		for ($i = 0, $n = count($users); $i < $n; $i++)
1216
		{
1217
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1218
			if ($data == null)
1219
				continue;
1220
1221
			$loaded_ids[] = $data['id_member'];
1222
			$user_profile[$data['id_member']] = $data;
1223
			unset($users[$i]);
1224
		}
1225
	}
1226
1227
	// Used by default
1228
	$select_columns = '
1229
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1230
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1231
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1232
			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,
1233
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1234
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1235
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1236
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1237
	$select_tables = '
1238
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1239
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1240
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1241
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1242
1243
	// We add or replace according the the set
1244
	switch ($set)
1245
	{
1246
		case 'normal':
1247
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1248
			break;
1249
		case 'profile':
1250
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1251
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1252
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1253
			break;
1254
		case 'minimal':
1255
			$select_columns = '
1256
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1257
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1258
			$select_tables = '';
1259
			break;
1260
		default:
1261
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1262
	}
1263
1264
	// Allow mods to easily add to the selected member data
1265
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1266
1267
	if (!empty($users))
1268
	{
1269
		// Load the member's data.
1270
		$request = $smcFunc['db_query']('', '
1271
			SELECT' . $select_columns . '
1272
			FROM {db_prefix}members AS mem' . $select_tables . '
1273
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1274
			array(
1275
				'blank_string' => '',
1276
				'users' => $users,
1277
			)
1278
		);
1279
		$new_loaded_ids = array();
1280
		while ($row = $smcFunc['db_fetch_assoc']($request))
1281
		{
1282
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1283
			$row['avatar_original'] = $row['avatar'];
1284
1285
			// Take care of proxying avatar if required, do this here for maximum reach
1286 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...
1287
				$row['avatar'] = $boardurl . '/proxy.php?request=' . urlencode($row['avatar']) . '&hash=' . md5($row['avatar'] . $image_proxy_secret);
1288
1289
			if (isset($row['member_ip']))
1290
				$row['member_ip'] = inet_dtop($row['member_ip']);
1291
			if (isset($row['member_ip2']))
1292
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1293
			$new_loaded_ids[] = $row['id_member'];
1294
			$loaded_ids[] = $row['id_member'];
1295
			$row['options'] = array();
1296
			$user_profile[$row['id_member']] = $row;
1297
		}
1298
		$smcFunc['db_free_result']($request);
1299
	}
1300
1301 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...
1302
	{
1303
		$request = $smcFunc['db_query']('', '
1304
			SELECT id_member, variable, value
1305
			FROM {db_prefix}themes
1306
			WHERE id_member IN ({array_int:loaded_ids})',
1307
			array(
1308
				'loaded_ids' => $new_loaded_ids,
1309
			)
1310
		);
1311
		while ($row = $smcFunc['db_fetch_assoc']($request))
1312
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1313
		$smcFunc['db_free_result']($request);
1314
	}
1315
1316
	$additional_mods = array();
1317
1318
	// Are any of these users in groups assigned to moderate this board?
1319
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1320
	{
1321
		foreach ($loaded_ids as $a_member)
1322
		{
1323
			if (!empty($user_profile[$a_member]['additional_groups']))
1324
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1325
			else
1326
				$groups = array($user_profile[$a_member]['id_group']);
1327
1328
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1329
1330
			if (!empty($temp))
1331
			{
1332
				$additional_mods[] = $a_member;
1333
			}
1334
		}
1335
	}
1336
1337
	if (!empty($new_loaded_ids) && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1338
	{
1339
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1340
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1341
	}
1342
1343
	// Are we loading any moderators?  If so, fix their group data...
1344
	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)
1345
	{
1346 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...
1347
		{
1348
			$request = $smcFunc['db_query']('', '
1349
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1350
				FROM {db_prefix}membergroups
1351
				WHERE id_group = {int:moderator_group}
1352
				LIMIT 1',
1353
				array(
1354
					'moderator_group' => 3,
1355
				)
1356
			);
1357
			$row = $smcFunc['db_fetch_assoc']($request);
1358
			$smcFunc['db_free_result']($request);
1359
1360
			cache_put_data('moderator_group_info', $row, 480);
1361
		}
1362
1363
		foreach ($temp_mods as $id)
1364
		{
1365
			// By popular demand, don't show admins or global moderators as moderators.
1366
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1367
				$user_profile[$id]['member_group'] = $row['member_group'];
1368
1369
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1370
			if (!empty($row['icons']))
1371
				$user_profile[$id]['icons'] = $row['icons'];
1372
			if (!empty($row['member_group_color']))
1373
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1374
		}
1375
	}
1376
1377
	return $loaded_ids;
1378
}
1379
1380
/**
1381
 * Loads the user's basic values... meant for template/theme usage.
1382
 *
1383
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1384
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1385
 * @return boolean Whether or not the data was loaded successfully
1386
 */
1387
function loadMemberContext($user, $display_custom_fields = false)
1388
{
1389
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1390
	global $context, $modSettings, $settings, $smcFunc;
1391
	static $dataLoaded = array();
1392
	static $loadedLanguages = array();
1393
1394
	// If this person's data is already loaded, skip it.
1395
	if (isset($dataLoaded[$user]))
1396
		return true;
1397
1398
	// We can't load guests or members not loaded by loadMemberData()!
1399
	if ($user == 0)
1400
		return false;
1401
	if (!isset($user_profile[$user]))
1402
	{
1403
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1404
		return false;
1405
	}
1406
1407
	// Well, it's loaded now anyhow.
1408
	$dataLoaded[$user] = true;
1409
	$profile = $user_profile[$user];
1410
1411
	// Censor everything.
1412
	censorText($profile['signature']);
1413
	censorText($profile['personal_text']);
1414
1415
	// Set things up to be used before hand.
1416
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1417
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1418
1419
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1420
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1421
	// Setup the buddy status here (One whole in_array call saved :P)
1422
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1423
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1424
1425
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1426
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1427
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1428
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1429
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1430
	else //not set, bye bye
1431
		$group_icon_url = '';
1432
1433
	// These minimal values are always loaded
1434
	$memberContext[$user] = array(
1435
		'username' => $profile['member_name'],
1436
		'name' => $profile['real_name'],
1437
		'id' => $profile['id_member'],
1438
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1439
		'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>',
1440
		'email' => $profile['email_address'],
1441
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1442
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1443
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1444
	);
1445
1446
	// If the set isn't minimal then load the monstrous array.
1447
	if ($context['loadMemberContext_set'] != 'minimal')
1448
	{
1449
		// Go the extra mile and load the user's native language name.
1450
		if (empty($loadedLanguages))
1451
			$loadedLanguages = getLanguages();
1452
1453
		$memberContext[$user] += array(
1454
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1455
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1456
			'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>',
1457
			'is_buddy' => $profile['buddy'],
1458
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1459
			'buddies' => $buddy_list,
1460
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1461
			'blurb' => $profile['personal_text'],
1462
			'website' => array(
1463
				'title' => $profile['website_title'],
1464
				'url' => $profile['website_url'],
1465
			),
1466
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1467
			'signature' => $profile['signature'],
1468
			'real_posts' => $profile['posts'],
1469
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1470
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1471
			'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...
1472
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1473
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1474
			'online' => array(
1475
				'is_online' => $profile['is_online'],
1476
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1477
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1478
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1479
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1480
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1481
			),
1482
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1483
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1484
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1485
			'options' => $profile['options'],
1486
			'is_guest' => false,
1487
			'group' => $profile['member_group'],
1488
			'group_color' => $profile['member_group_color'],
1489
			'group_id' => $profile['id_group'],
1490
			'post_group' => $profile['post_group'],
1491
			'post_group_color' => $profile['post_group_color'],
1492
			'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]),
1493
			'warning' => $profile['warning'],
1494
			'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' : (''))),
1495
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1496
			'custom_fields' => array(),
1497
		);
1498
	}
1499
1500
	// If the set isn't minimal then load their avatar as well.
1501
	if ($context['loadMemberContext_set'] != 'minimal')
1502
	{
1503
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1504
		{
1505
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1506
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1507
			else
1508
				$image = get_gravatar_url($profile['email_address']);
1509
		}
1510
		else
1511
		{
1512
			// So it's stored in the member table?
1513
			if (!empty($profile['avatar']))
1514
			{
1515
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1516
			}
1517
			elseif (!empty($profile['filename']))
1518
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1519
			// Right... no avatar...use the default one
1520
			else
1521
				$image = $modSettings['avatar_url'] . '/default.png';
1522
		}
1523
		if (!empty($image))
1524
			$memberContext[$user]['avatar'] = array(
1525
				'name' => $profile['avatar'],
1526
				'image' => '<img class="avatar" src="' . $image . '" alt="avatar_' . $profile['member_name'] . '">',
1527
				'href' => $image,
1528
				'url' => $image,
1529
			);
1530
	}
1531
1532
	// Are we also loading the members custom fields into context?
1533
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1534
	{
1535
		$memberContext[$user]['custom_fields'] = array();
1536
1537
		if (!isset($context['display_fields']))
1538
			$context['display_fields'] = smf_json_decode($modSettings['displayFields'], true);
1539
1540
		foreach ($context['display_fields'] as $custom)
1541
		{
1542
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1543
				continue;
1544
1545
			$value = $profile['options'][$custom['col_name']];
1546
1547
			// Don't show the "disabled" option for the "gender" field.
1548
			if ($custom['col_name'] == 'cust_gender' && $value == 'Disabled')
1549
				continue;
1550
1551
			// BBC?
1552
			if ($custom['bbc'])
1553
				$value = parse_bbc($value);
1554
			// ... or checkbox?
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1555
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1556
				$value = $value ? $txt['yes'] : $txt['no'];
1557
1558
			// Enclosing the user input within some other text?
1559 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...
1560
				$value = strtr($custom['enclose'], array(
1561
					'{SCRIPTURL}' => $scripturl,
1562
					'{IMAGES_URL}' => $settings['images_url'],
1563
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1564
					'{INPUT}' => $value,
1565
				));
1566
1567
			$memberContext[$user]['custom_fields'][] = array(
1568
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1569
				'col_name' => $custom['col_name'],
1570
				'value' => un_htmlspecialchars($value),
1571
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1572
			);
1573
		}
1574
	}
1575
1576
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1577
	return true;
1578
}
1579
1580
/**
1581
 * Loads the user's custom profile fields
1582
 *
1583
 * @param integer|array $users A single user ID or an array of user IDs
1584
 * @param string|array $params Either a string or an array of strings with profile field names
1585
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1586
 */
1587
function loadMemberCustomFields($users, $params)
1588
{
1589
	global $smcFunc, $txt, $scripturl, $settings;
1590
1591
	// Do not waste my time...
1592
	if (empty($users) || empty($params))
1593
		return false;
1594
1595
	// Make sure it's an array.
1596
	$users = !is_array($users) ? array($users) : array_unique($users);
1597
	$params = !is_array($params) ? array($params) : array_unique($params);
1598
	$return = array();
1599
1600
	$request = $smcFunc['db_query']('', '
1601
		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,
1602
		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
1603
		FROM {db_prefix}themes AS t
1604
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1605
		WHERE id_member IN ({array_int:loaded_ids})
1606
			AND variable IN ({array_string:params})
1607
		ORDER BY field_order',
1608
		array(
1609
			'loaded_ids' => $users,
1610
			'params' => $params,
1611
		)
1612
	);
1613
1614
	while ($row = $smcFunc['db_fetch_assoc']($request))
1615
	{
1616
		// BBC?
1617
		if (!empty($row['bbc']))
1618
			$row['value'] = parse_bbc($row['value']);
1619
1620
		// ... or checkbox?
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1621
		elseif (isset($row['type']) && $row['type'] == 'check')
1622
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1623
1624
		// Enclosing the user input within some other text?
1625
		if (!empty($row['enclose']))
1626
			$row['value'] = strtr($row['enclose'], array(
1627
				'{SCRIPTURL}' => $scripturl,
1628
				'{IMAGES_URL}' => $settings['images_url'],
1629
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1630
				'{INPUT}' => un_htmlspecialchars($row['value']),
1631
			));
1632
1633
		// Send a simple array if there is just 1 param
1634
		if (count($params) == 1)
1635
			$return[$row['id_member']] = $row;
1636
1637
		// More than 1? knock yourself out...
1638
		else
1639
		{
1640
			if (!isset($return[$row['id_member']]))
1641
				$return[$row['id_member']] = array();
1642
1643
			$return[$row['id_member']][$row['variable']] = $row;
1644
		}
1645
	}
1646
1647
	$smcFunc['db_free_result']($request);
1648
1649
	return !empty($return) ? $return : false;
1650
}
1651
1652
/**
1653
 * Loads information about what browser the user is viewing with and places it in $context
1654
 *  - uses the class from {@link Class-BrowserDetect.php}
1655
 */
1656
function detectBrowser()
1657
{
1658
	// Load the current user's browser of choice
1659
	$detector = new browser_detector;
1660
	$detector->detectBrowser();
1661
}
1662
1663
/**
1664
 * Are we using this browser?
1665
 *
1666
 * Wrapper function for detectBrowser
1667
 * @param string $browser The browser we are checking for.
1668
 * @return bool Whether or not the current browser is what we're looking for
1669
*/
1670
function isBrowser($browser)
1671
{
1672
	global $context;
1673
1674
	// Don't know any browser!
1675
	if (empty($context['browser']))
1676
		detectBrowser();
1677
1678
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1679
}
1680
1681
/**
1682
 * Load a theme, by ID.
1683
 *
1684
 * @param int $id_theme The ID of the theme to load
1685
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1686
 */
1687
function loadTheme($id_theme = 0, $initialize = true)
1688
{
1689
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1690
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1691
	global $context, $settings, $options, $sourcedir, $ssi_theme, $smcFunc, $language, $board, $image_proxy_enabled;
1692
1693
	// The theme was specified by parameter.
1694
	if (!empty($id_theme))
1695
		$id_theme = (int) $id_theme;
1696
	// The theme was specified by REQUEST.
1697 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...
1698
	{
1699
		$id_theme = (int) $_REQUEST['theme'];
1700
		$_SESSION['id_theme'] = $id_theme;
1701
	}
1702
	// The theme was specified by REQUEST... previously.
1703 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...
1704
		$id_theme = (int) $_SESSION['id_theme'];
1705
	// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1706
	elseif (!empty($user_info['theme']) && !isset($_REQUEST['theme']))
1707
		$id_theme = $user_info['theme'];
1708
	// The theme was specified by the board.
1709
	elseif (!empty($board_info['theme']))
1710
		$id_theme = $board_info['theme'];
1711
	// The theme is the forum's default.
1712
	else
1713
		$id_theme = $modSettings['theme_guests'];
1714
1715
	// Verify the id_theme... no foul play.
1716
	// Always allow the board specific theme, if they are overriding.
1717
	if (!empty($board_info['theme']) && $board_info['override_theme'])
1718
		$id_theme = $board_info['theme'];
1719
	// If they have specified a particular theme to use with SSI allow it to be used.
1720
	elseif (!empty($ssi_theme) && $id_theme == $ssi_theme)
1721
		$id_theme = (int) $id_theme;
1722
	elseif (!empty($modSettings['enableThemes']) && !allowedTo('admin_forum'))
1723
	{
1724
		$themes = explode(',', $modSettings['enableThemes']);
1725
		if (!in_array($id_theme, $themes))
1726
			$id_theme = $modSettings['theme_guests'];
1727
		else
1728
			$id_theme = (int) $id_theme;
1729
	}
1730
	else
1731
		$id_theme = (int) $id_theme;
1732
1733
	$member = empty($user_info['id']) ? -1 : $user_info['id'];
1734
1735
	// Disable image proxy if we don't have SSL enabled
1736
	if (empty($modSettings['force_ssl']) || $modSettings['force_ssl'] < 2)
1737
		$image_proxy_enabled = false;
1738
1739
	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'])
1740
	{
1741
		$themeData = $temp;
1742
		$flag = true;
1743
	}
1744
	elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
1745
		$themeData = $temp + array($member => array());
1746
	else
1747
		$themeData = array(-1 => array(), 0 => array(), $member => array());
1748
1749
	if (empty($flag))
1750
	{
1751
		// Load variables from the current or default theme, global or this user's.
1752
		$result = $smcFunc['db_query']('', '
1753
			SELECT variable, value, id_member, id_theme
1754
			FROM {db_prefix}themes
1755
			WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1756
				AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)'),
1757
			array(
1758
				'id_theme' => $id_theme,
1759
				'id_member' => $member,
1760
			)
1761
		);
1762
		// Pick between $settings and $options depending on whose data it is.
1763
		while ($row = $smcFunc['db_fetch_assoc']($result))
1764
		{
1765
			// There are just things we shouldn't be able to change as members.
1766
			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')))
1767
				continue;
1768
1769
			// If this is the theme_dir of the default theme, store it.
1770
			if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1771
				$themeData[0]['default_' . $row['variable']] = $row['value'];
1772
1773
			// If this isn't set yet, is a theme option, or is not the default theme..
1774
			if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1775
				$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1776
		}
1777
		$smcFunc['db_free_result']($result);
1778
1779
		if (!empty($themeData[-1]))
1780
			foreach ($themeData[-1] as $k => $v)
1781
			{
1782
				if (!isset($themeData[$member][$k]))
1783
					$themeData[$member][$k] = $v;
1784
			}
1785
1786
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
1787
			cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1788
		// Only if we didn't already load that part of the cache...
1789
		elseif (!isset($temp))
1790
			cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1791
	}
1792
1793
	$settings = $themeData[0];
1794
	$options = $themeData[$member];
1795
1796
	$settings['theme_id'] = $id_theme;
1797
1798
	$settings['actual_theme_url'] = $settings['theme_url'];
1799
	$settings['actual_images_url'] = $settings['images_url'];
1800
	$settings['actual_theme_dir'] = $settings['theme_dir'];
1801
1802
	$settings['template_dirs'] = array();
1803
	// This theme first.
1804
	$settings['template_dirs'][] = $settings['theme_dir'];
1805
1806
	// Based on theme (if there is one).
1807
	if (!empty($settings['base_theme_dir']))
1808
		$settings['template_dirs'][] = $settings['base_theme_dir'];
1809
1810
	// Lastly the default theme.
1811 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...
1812
		$settings['template_dirs'][] = $settings['default_theme_dir'];
1813
1814
	if (!$initialize)
1815
		return;
1816
1817
	// Check to see if we're forcing SSL
1818
	if (!empty($modSettings['force_ssl']) && $modSettings['force_ssl'] == 2 && empty($maintenance) &&
1819
		(!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off') && SMF != 'SSI')
1820
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')));
1821
1822
	// Check to see if they're accessing it from the wrong place.
1823
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1824
	{
1825
		$detected_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on' ? 'https://' : 'http://';
1826
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1827
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1828
		if ($temp != '/')
1829
			$detected_url .= $temp;
1830
	}
1831
	if (isset($detected_url) && $detected_url != $boardurl)
1832
	{
1833
		// Try #1 - check if it's in a list of alias addresses.
1834
		if (!empty($modSettings['forum_alias_urls']))
1835
		{
1836
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1837
1838
			foreach ($aliases as $alias)
1839
			{
1840
				// Rip off all the boring parts, spaces, etc.
1841
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
1842
					$do_fix = true;
1843
			}
1844
		}
1845
1846
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
1847
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI')
1848
		{
1849
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
1850
			if (empty($_GET))
1851
				redirectexit('wwwRedirect');
1852
			else
1853
			{
1854
				list ($k, $v) = each($_GET);
1855
1856
				if ($k != 'wwwRedirect')
1857
					redirectexit('wwwRedirect;' . $k . '=' . $v);
1858
			}
1859
		}
1860
1861
		// #3 is just a check for SSL...
1862
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
1863
			$do_fix = true;
1864
1865
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
1866
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
1867
		{
1868
			// Caching is good ;).
1869
			$oldurl = $boardurl;
1870
1871
			// Fix $boardurl and $scripturl.
1872
			$boardurl = $detected_url;
1873
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
1874
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
1875
1876
			// Fix the theme urls...
1877
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
1878
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
1879
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
1880
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
1881
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
1882
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
1883
1884
			// And just a few mod settings :).
1885
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
1886
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
1887
1888
			// Clean up after loadBoard().
1889
			if (isset($board_info['moderators']))
1890
			{
1891
				foreach ($board_info['moderators'] as $k => $dummy)
1892
				{
1893
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
1894
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
1895
				}
1896
			}
1897
			foreach ($context['linktree'] as $k => $dummy)
1898
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
1899
		}
1900
	}
1901
	// Set up the contextual user array.
1902
	if (!empty($user_info))
1903
	{
1904
		$context['user'] = array(
1905
			'id' => $user_info['id'],
1906
			'is_logged' => !$user_info['is_guest'],
1907
			'is_guest' => &$user_info['is_guest'],
1908
			'is_admin' => &$user_info['is_admin'],
1909
			'is_mod' => &$user_info['is_mod'],
1910
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
1911
			'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'])))),
1912
			'username' => $user_info['username'],
1913
			'language' => $user_info['language'],
1914
			'email' => $user_info['email'],
1915
			'ignoreusers' => $user_info['ignoreusers'],
1916
		);
1917
		if (!$context['user']['is_guest'])
1918
			$context['user']['name'] = $user_info['name'];
1919 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...
1920
			$context['user']['name'] = $txt['guest_title'];
1921
1922
		// Determine the current smiley set.
1923
		$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'];
1924
		$context['user']['smiley_set'] = $user_info['smiley_set'];
1925
	}
1926
	else
1927
	{
1928
		$context['user'] = array(
1929
			'id' => -1,
1930
			'is_logged' => false,
1931
			'is_guest' => true,
1932
			'is_mod' => false,
1933
			'can_mod' => false,
1934
			'name' => $txt['guest_title'],
1935
			'language' => $language,
1936
			'email' => '',
1937
			'ignoreusers' => array(),
1938
		);
1939
	}
1940
1941
	// Some basic information...
1942
	if (!isset($context['html_headers']))
1943
		$context['html_headers'] = '';
1944
	if (!isset($context['javascript_files']))
1945
		$context['javascript_files'] = array();
1946
	if (!isset($context['css_files']))
1947
		$context['css_files'] = array();
1948
	if (!isset($context['css_header']))
1949
		$context['css_header'] = array();
1950
	if (!isset($context['javascript_inline']))
1951
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
1952
	if (!isset($context['javascript_vars']))
1953
		$context['javascript_vars'] = array();
1954
1955
	$context['login_url'] = (!empty($modSettings['force_ssl']) && $modSettings['force_ssl'] < 2 ? strtr($scripturl, array('http://' => 'https://')) : $scripturl) . '?action=login2';
1956
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
1957
	$context['session_var'] = $_SESSION['session_var'];
1958
	$context['session_id'] = $_SESSION['session_value'];
1959
	$context['forum_name'] = $mbname;
1960
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
1961
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
1962
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
1963
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
1964
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
1965
	if (isset($modSettings['load_average']))
1966
		$context['load_average'] = $modSettings['load_average'];
1967
1968
	// Detect the browser. This is separated out because it's also used in attachment downloads
1969
	detectBrowser();
1970
1971
	// Set the top level linktree up.
1972
	array_unshift($context['linktree'], array(
1973
		'url' => $scripturl,
1974
		'name' => $context['forum_name_html_safe']
1975
	));
1976
1977
	// This allows sticking some HTML on the page output - useful for controls.
1978
	$context['insert_after_template'] = '';
1979
1980
	if (!isset($txt))
1981
		$txt = array();
1982
1983
	$simpleActions = array(
1984
		'findmember',
1985
		'helpadmin',
1986
		'printpage',
1987
		'spellcheck',
1988
	);
1989
1990
	// Parent action => array of areas
1991
	$simpleAreas = array(
1992
		'profile' => array('popup', 'alerts_popup',),
1993
	);
1994
1995
	// Parent action => array of subactions
1996
	$simpleSubActions = array(
1997
		'pm' => array('popup',),
1998
		'signup' => array('usernamecheck'),
1999
	);
2000
2001
	// Extra params like ;preview ;js, etc.
2002
	$extraParams = array(
2003
		'preview',
2004
		'splitjs',
2005
	);
2006
2007
	// Actions that specifically uses XML output.
2008
	$xmlActions = array(
2009
		'quotefast',
2010
		'jsmodify',
2011
		'xmlhttp',
2012
		'post2',
2013
		'suggest',
2014
		'stats',
2015
		'notifytopic',
2016
		'notifyboard',
2017
	);
2018
2019
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2020
2021
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2022
	(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2023
	(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2024
2025
	// See if theres any extra param to check.
2026
	$requiresXML = false;
2027
	foreach ($extraParams as $key => $extra)
2028
		if (isset($_REQUEST[$extra]))
2029
			$requiresXML = true;
2030
2031
	// Output is fully XML, so no need for the index template.
2032
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2033
	{
2034
		loadLanguage('index+Modifications');
2035
		loadTemplate('Xml');
2036
		$context['template_layers'] = array();
2037
	}
2038
2039
	// These actions don't require the index template at all.
2040
	elseif (!empty($context['simple_action']))
2041
	{
2042
		loadLanguage('index+Modifications');
2043
		$context['template_layers'] = array();
2044
	}
2045
2046
	else
2047
	{
2048
		// Custom templates to load, or just default?
2049
		if (isset($settings['theme_templates']))
2050
			$templates = explode(',', $settings['theme_templates']);
2051
		else
2052
			$templates = array('index');
2053
2054
		// Load each template...
2055
		foreach ($templates as $template)
2056
			loadTemplate($template);
2057
2058
		// ...and attempt to load their associated language files.
2059
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2060
		loadLanguage($required_files, '', false);
2061
2062
		// Custom template layers?
2063
		if (isset($settings['theme_layers']))
2064
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2065
		else
2066
			$context['template_layers'] = array('html', 'body');
2067
	}
2068
2069
	// Initialize the theme.
2070
	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...
2071
2072
	// Allow overriding the board wide time/number formats.
2073
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2074
		$user_info['time_format'] = $txt['time_format'];
2075
2076
	// Set the character set from the template.
2077
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2078
	$context['utf8'] = $context['character_set'] === 'UTF-8';
2079
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2080
2081
	// Guests may still need a name.
2082 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...
2083
		$context['user']['name'] = $txt['guest_title'];
2084
2085
	// Any theme-related strings that need to be loaded?
2086
	if (!empty($settings['require_theme_strings']))
2087
		loadLanguage('ThemeStrings', '', false);
2088
2089
	// Make a special URL for the language.
2090
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2091
2092
	// And of course, let's load the default CSS file.
2093
	loadCSSFile('index.css', array('minimize' => true), 'smf_index');
2094
2095
	// Here is my luvly Responsive CSS
2096
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true), 'smf_responsive');
2097
2098
	if ($context['right_to_left'])
2099
		loadCSSFile('rtl.css', array(), 'smf_rtl');
2100
2101
	// We allow theme variants, because we're cool.
2102
	$context['theme_variant'] = '';
2103
	$context['theme_variant_url'] = '';
2104
	if (!empty($settings['theme_variants']))
2105
	{
2106
		// Overriding - for previews and that ilk.
2107
		if (!empty($_REQUEST['variant']))
2108
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2109
		// User selection?
2110
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2111
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2112
		// If not a user variant, select the default.
2113
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2114
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2115
2116
		// Do this to keep things easier in the templates.
2117
		$context['theme_variant'] = '_' . $context['theme_variant'];
2118
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2119
2120
		if (!empty($context['theme_variant']))
2121
		{
2122
			loadCSSFile('index' . $context['theme_variant'] . '.css', array(), 'smf_index' . $context['theme_variant']);
2123
			if ($context['right_to_left'])
2124
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array(), 'smf_rtl' . $context['theme_variant']);
2125
		}
2126
	}
2127
2128
	// Let's be compatible with old themes!
2129
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2130
		$context['template_layers'] = array('main');
2131
2132
	$context['tabindex'] = 1;
2133
2134
	// Compatibility.
2135
	if (!isset($settings['theme_version']))
2136
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2137
2138
	// Default JS variables for use in every theme
2139
	$context['javascript_vars'] = array(
2140
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2141
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2142
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2143
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2144
		'smf_scripturl' => '"' . $scripturl . '"',
2145
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2146
		'smf_charset' => '"' . $context['character_set'] . '"',
2147
		'smf_session_id' => '"' . $context['session_id'] . '"',
2148
		'smf_session_var' => '"' . $context['session_var'] . '"',
2149
		'smf_member_id' => $context['user']['id'],
2150
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2151
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2152
	);
2153
2154
	// Add the JQuery library to the list of files to load.
2155
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2156
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js', array('external' => true), 'smf_jquery');
2157
2158
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2159
		loadJavaScriptFile('jquery-3.1.1.min.js', array('seed' => false), 'smf_jquery');
2160
2161
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2162
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2163
2164
	// Auto loading? template_javascript() will take care of the local half of this.
2165
	else
2166
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js', array('external' => true), 'smf_jquery');
2167
2168
	// Queue our JQuery plugins!
2169
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2170
	if (!$user_info['is_guest'])
2171
	{
2172
		loadJavaScriptFile('jquery.custom-scrollbar.js', array(), 'smf_jquery_scrollbar');
2173
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2174
	}
2175
2176
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2177
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2178
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2179
2180
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2181
	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())
2182
	{
2183
		if (isBrowser('possibly_robot'))
2184
		{
2185
			// @todo Maybe move this somewhere better?!
2186
			require_once($sourcedir . '/ScheduledTasks.php');
2187
2188
			// What to do, what to do?!
2189
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2190
				AutoTask();
2191
			else
2192
				ReduceMailQueue();
2193
		}
2194
		else
2195
		{
2196
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2197
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
2198
2199
			addInlineJavaScript('
2200
		function smfAutoTask()
2201
		{
2202
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2203
		}
2204
		window.setTimeout("smfAutoTask();", 1);');
2205
		}
2206
	}
2207
2208
	// And we should probably trigger the cron too.
2209
	if (empty($modSettings['cron_is_real_cron']))
2210
	{
2211
		$ts = time();
2212
		$ts -= $ts % 15;
2213
		addInlineJavaScript('
2214
	function triggerCron()
2215
	{
2216
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2217
	}
2218
	window.setTimeout(triggerCron, 1);', true);
2219
	}
2220
2221
	// Filter out the restricted boards from the linktree
2222
	if (!$user_info['is_admin'] && !empty($board))
2223
	{
2224
		foreach ($context['linktree'] as $k => $element)
2225
		{
2226
			if (!empty($element['groups']) &&
2227
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2228
				(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2229
			{
2230
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2231
				$context['linktree'][$k]['extra_before'] = '<i>';
2232
				$context['linktree'][$k]['extra_after'] = '</i>';
2233
				unset($context['linktree'][$k]['url']);
2234
			}
2235
		}
2236
	}
2237
2238
	// Any files to include at this point?
2239 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...
2240
	{
2241
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2242
		foreach ($theme_includes as $include)
2243
		{
2244
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2245
			if (file_exists($include))
2246
				require_once($include);
2247
		}
2248
	}
2249
2250
	// Call load theme integration functions.
2251
	call_integration_hook('integrate_load_theme');
2252
2253
	// We are ready to go.
2254
	$context['theme_loaded'] = true;
2255
}
2256
2257
/**
2258
 * Load a template - if the theme doesn't include it, use the default.
2259
 * What this function does:
2260
 *  - loads a template file with the name template_name from the current, default, or base theme.
2261
 *  - detects a wrong default theme directory and tries to work around it.
2262
 *
2263
 * @uses the template_include() function to include the file.
2264
 * @param string $template_name The name of the template to load
2265
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2266
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2267
 * @return boolean Whether or not the template was loaded
2268
 */
2269
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2270
{
2271
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
2272
2273
	// Do any style sheets first, cause we're easy with those.
2274
	if (!empty($style_sheets))
2275
	{
2276
		if (!is_array($style_sheets))
2277
			$style_sheets = array($style_sheets);
2278
2279
		foreach ($style_sheets as $sheet)
2280
			loadCSSFile($sheet . '.css', array(), $sheet);
2281
	}
2282
2283
	// No template to load?
2284
	if ($template_name === false)
2285
		return true;
2286
2287
	$loaded = false;
2288
	foreach ($settings['template_dirs'] as $template_dir)
2289
	{
2290
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2291
		{
2292
			$loaded = true;
2293
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2294
			break;
2295
		}
2296
	}
2297
2298
	if ($loaded)
2299
	{
2300
		if ($db_show_debug === true)
2301
			$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 2288. 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...
2302
2303
		// If they have specified an initialization function for this template, go ahead and call it now.
2304
		if (function_exists('template_' . $template_name . '_init'))
2305
			call_user_func('template_' . $template_name . '_init');
2306
	}
2307
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2308
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2309
	{
2310
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2311
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2312
2313 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...
2314
		{
2315
			loadLanguage('Errors');
2316
			echo '
2317
<div class="alert errorbox">
2318
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2319
</div>';
2320
		}
2321
2322
		loadTemplate($template_name);
2323
	}
2324
	// Cause an error otherwise.
2325
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2326
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2327
	elseif ($fatal)
2328
		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'));
2329
	else
2330
		return false;
2331
}
2332
2333
/**
2334
 * Load a sub-template.
2335
 * What it does:
2336
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2337
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2338
 *	for debugging purposes.
2339
 *
2340
 * @todo get rid of reading $_REQUEST directly
2341
 *
2342
 * @param string $sub_template_name The name of the sub-template to load
2343
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2344
 */
2345
function loadSubTemplate($sub_template_name, $fatal = false)
2346
{
2347
	global $context, $txt, $db_show_debug;
2348
2349
	if ($db_show_debug === true)
2350
		$context['debug']['sub_templates'][] = $sub_template_name;
2351
2352
	// Figure out what the template function is named.
2353
	$theme_function = 'template_' . $sub_template_name;
2354
	if (function_exists($theme_function))
2355
		$theme_function();
2356
	elseif ($fatal === false)
2357
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2358
	elseif ($fatal !== 'ignore')
2359
		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'));
2360
2361
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2362
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2363
	{
2364
		echo '
2365
<div class="warningbox">---- ', $sub_template_name, ' ends ----</div>';
2366
	}
2367
}
2368
2369
/**
2370
 * Add a CSS file for output later
2371
 *
2372
 * @param string $fileName The name of the file to load
2373
 * @param array $params An array of parameters
2374
 * Keys are the following:
2375
 * 	- ['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
2376
 * 	- ['default_theme'] (true/false): force use of default theme url
2377
 * 	- ['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
2378
 *  - ['validate'] (true/false): if true script will validate the local file exists
2379
 *  - ['rtl'] (string): additional file to load in RTL mode
2380
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2381
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2382
 * @param string $id An ID to stick on the end of the filename for caching purposes
2383
 */
2384
function loadCSSFile($fileName, $params = array(), $id = '')
2385
{
2386
	global $settings, $context, $modSettings;
2387
2388
	$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']) : '');
2389
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2390
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2391
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2392
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2393
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2394
2395
	// If this is an external file, automatically set this to false.
2396
	if (!empty($params['external']))
2397
		$params['minimize'] = false;
2398
2399
	// Account for shorthand like admin.css?alp21 filenames
2400
	$has_seed = strpos($fileName, '.css?');
2401
	$id = empty($id) ? strtr(basename(str_replace('.css', '', $fileName)), '?', '_') : $id;
2402
2403
	// Is this a local file?
2404 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...
2405
	{
2406
		// Are we validating the the file exists?
2407
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/css/' . $fileName))
2408
		{
2409
			// Maybe the default theme has it?
2410
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/css/' . $fileName))
2411
			{
2412
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2413
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2414
			}
2415
2416
			else
2417
				$fileUrl = false;
2418
		}
2419
2420
		else
2421
		{
2422
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2423
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2424
		}
2425
	}
2426
2427
	// An external file doesn't have a filepath. Mock one for simplicity.
2428
	else
2429
	{
2430
		$fileUrl = $fileName;
2431
		$filePath = $fileName;
2432
	}
2433
2434
	// Add it to the array for use in the template
2435 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...
2436
		$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...
2437
2438
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2439
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2440
}
2441
2442
/**
2443
 * Add a block of inline css code to be executed later
2444
 *
2445
 * - only use this if you have to, generally external css files are better, but for very small changes
2446
 *   or for scripts that require help from PHP/whatever, this can be useful.
2447
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2448
 *
2449
 * @param string $css Some css code
2450
 * @return void|bool Adds the CSS to the $context['css_header'] array or returns if no CSS is specified
2451
 */
2452
function addInlineCss($css)
2453
{
2454
	global $context;
2455
2456
	// Gotta add something...
2457
	if (empty($css))
2458
		return false;
2459
2460
	$context['css_header'][] = $css;
2461
}
2462
2463
/**
2464
 * Add a Javascript file for output later
2465
 *
2466
 * @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...
2467
 * @param array $params An array of parameter info
2468
 * Keys are the following:
2469
 * 	- ['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
2470
 * 	- ['default_theme'] (true/false): force use of default theme url
2471
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2472
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2473
 *	default theme if not found in the current theme
2474
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2475
 *  - ['validate'] (true/false): if true script will validate the local file exists
2476
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2477
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2478
 *
2479
 * @param string $id An ID to stick on the end of the filename
2480
 */
2481
function loadJavaScriptFile($fileName, $params = array(), $id = '')
2482
{
2483
	global $settings, $context, $modSettings;
2484
2485
	$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']) : '');
2486
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2487
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2488
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2489
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2490
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2491
2492
	// If this is an external file, automatically set this to false.
2493
	if (!empty($params['external']))
2494
		$params['minimize'] = false;
2495
2496
	// Account for shorthand like admin.js?alp21 filenames
2497
	$has_seed = strpos($fileName, '.js?');
2498
	$id = empty($id) ? strtr(basename(str_replace('.js', '', $fileName)), '?', '_') : $id;
2499
2500
	// Is this a local file?
2501 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...
2502
	{
2503
		// Are we validating it exists on disk?
2504
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/scripts/' . $fileName))
2505
		{
2506
			// Can't find it in this theme, how about the default?
2507
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/scripts/' . $fileName))
2508
			{
2509
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2510
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2511
			}
2512
2513
			else
2514
			{
2515
				$fileUrl = false;
2516
				$filePath = false;
2517
			}
2518
		}
2519
2520
		else
2521
		{
2522
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2523
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2524
		}
2525
	}
2526
2527
	// An external file doesn't have a filepath. Mock one for simplicity.
2528
	else
2529
	{
2530
		$fileUrl = $fileName;
2531
		$filePath = $fileName;
2532
	}
2533
2534
	// Add it to the array for use in the template
2535 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...
2536
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params);
2537
}
2538
2539
/**
2540
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2541
 * Cleaner and easier (for modders) than to use the function below.
2542
 *
2543
 * @param string $key The key for this variable
2544
 * @param string $value The value
2545
 * @param bool $escape Whether or not to escape the value
2546
 */
2547
function addJavaScriptVar($key, $value, $escape = false)
2548
{
2549
	global $context;
2550
2551
	if (!empty($key) && (!empty($value) || $value === '0'))
2552
		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
2553
}
2554
2555
/**
2556
 * Add a block of inline Javascript code to be executed later
2557
 *
2558
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2559
 *   or for scripts that require help from PHP/whatever, this can be useful.
2560
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2561
 *
2562
 * @param string $javascript Some JS code
2563
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2564
 * @return void|bool Adds the code to one of the $context['javascript_inline'] arrays or returns if no JS was specified
2565
 */
2566
function addInlineJavaScript($javascript, $defer = false)
2567
{
2568
	global $context;
2569
2570
	if (empty($javascript))
2571
		return false;
2572
2573
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2574
}
2575
2576
/**
2577
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2578
 *
2579
 * @param string $template_name The name of a template file
2580
 * @param string $lang A specific language to load this file from
2581
 * @param bool $fatal Whether to die with an error if it can't be loaded
2582
 * @param bool $force_reload Whether to load the file again if it's already loaded
2583
 * @return string The language actually loaded.
2584
 */
2585
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2586
{
2587
	global $user_info, $language, $settings, $context, $modSettings;
2588
	global $db_show_debug, $sourcedir, $txt, $birthdayEmails, $txtBirthdayEmails;
2589
	static $already_loaded = array();
2590
2591
	// Default to the user's language.
2592
	if ($lang == '')
2593
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2594
2595
	// Do we want the English version of language file as fallback?
2596
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2597
		loadLanguage($template_name, 'english', false);
2598
2599
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2600
		return $lang;
2601
2602
	// Make sure we have $settings - if not we're in trouble and need to find it!
2603
	if (empty($settings['default_theme_dir']))
2604
	{
2605
		require_once($sourcedir . '/ScheduledTasks.php');
2606
		loadEssentialThemeData();
2607
	}
2608
2609
	// What theme are we in?
2610
	$theme_name = basename($settings['theme_url']);
2611
	if (empty($theme_name))
2612
		$theme_name = 'unknown';
2613
2614
	// For each file open it up and write it out!
2615
	foreach (explode('+', $template_name) as $template)
2616
	{
2617
		// Obviously, the current theme is most important to check.
2618
		$attempts = array(
2619
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2620
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2621
		);
2622
2623
		// Do we have a base theme to worry about?
2624
		if (isset($settings['base_theme_dir']))
2625
		{
2626
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2627
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2628
		}
2629
2630
		// Fall back on the default theme if necessary.
2631
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2632
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2633
2634
		// Fall back on the English language if none of the preferred languages can be found.
2635
		if (!in_array('english', array($lang, $language)))
2636
		{
2637
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2638
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2639
		}
2640
2641
		// Try to find the language file.
2642
		$found = false;
2643
		foreach ($attempts as $k => $file)
2644
		{
2645
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2646
			{
2647
				// Include it!
2648
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2649
2650
				// Note that we found it.
2651
				$found = true;
2652
2653
				// setlocale is required for basename() & pathinfo() to work properly on the selected language
2654
				if (!empty($txt['lang_locale']) && !empty($modSettings['global_character_set']))
2655
					setlocale(LC_CTYPE, $txt['lang_locale'] . '.' . $modSettings['global_character_set']);
2656
2657
				break;
2658
			}
2659
		}
2660
2661
		// That couldn't be found!  Log the error, but *try* to continue normally.
2662
		if (!$found && $fatal)
2663
		{
2664
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2665
			break;
2666
		}
2667
2668
		// For the sake of backward compatibility
2669
		if (!empty($txt['emails']))
2670
		{
2671
			foreach ($txt['emails'] as $key => $value)
2672
			{
2673
				$txt[$key . '_subject'] = $value['subject'];
2674
				$txt[$key . '_body'] = $value['body'];
2675
			}
2676
			$txt['emails'] = array();
2677
		}
2678
		// For sake of backward compatibility: $birthdayEmails is supposed to be
2679
		// empty in a normal install. If it isn't it means the forum is using
2680
		// something "old" (it may be the translation, it may be a mod) and this
2681
		// code (like the piece above) takes care of converting it to the new format
2682
		if (!empty($birthdayEmails))
2683
		{
2684
			foreach ($birthdayEmails as $key => $value)
2685
			{
2686
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
2687
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
2688
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
2689
			}
2690
			$birthdayEmails = array();
2691
		}
2692
	}
2693
2694
	// Keep track of what we're up to soldier.
2695
	if ($db_show_debug === true)
2696
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
2697
2698
	// Remember what we have loaded, and in which language.
2699
	$already_loaded[$template_name] = $lang;
2700
2701
	// Return the language actually loaded.
2702
	return $lang;
2703
}
2704
2705
/**
2706
 * Get all parent boards (requires first parent as parameter)
2707
 * It finds all the parents of id_parent, and that board itself.
2708
 * Additionally, it detects the moderators of said boards.
2709
 *
2710
 * @param int $id_parent The ID of the parent board
2711
 * @return array An array of information about the boards found.
2712
 */
2713
function getBoardParents($id_parent)
2714
{
2715
	global $scripturl, $smcFunc;
2716
2717
	// First check if we have this cached already.
2718
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
2719
	{
2720
		$boards = array();
2721
		$original_parent = $id_parent;
2722
2723
		// Loop while the parent is non-zero.
2724
		while ($id_parent != 0)
2725
		{
2726
			$result = $smcFunc['db_query']('', '
2727
				SELECT
2728
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
2729
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
2730
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
2731
				FROM {db_prefix}boards AS b
2732
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
2733
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
2734
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
2735
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
2736
				WHERE b.id_board = {int:board_parent}',
2737
				array(
2738
					'board_parent' => $id_parent,
2739
				)
2740
			);
2741
			// In the EXTREMELY unlikely event this happens, give an error message.
2742
			if ($smcFunc['db_num_rows']($result) == 0)
2743
				fatal_lang_error('parent_not_found', 'critical');
2744
			while ($row = $smcFunc['db_fetch_assoc']($result))
2745
			{
2746
				if (!isset($boards[$row['id_board']]))
2747
				{
2748
					$id_parent = $row['id_parent'];
2749
					$boards[$row['id_board']] = array(
2750
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
2751
						'name' => $row['name'],
2752
						'level' => $row['child_level'],
2753
						'groups' => explode(',', $row['member_groups']),
2754
						'deny_groups' => explode(',', $row['deny_member_groups']),
2755
						'moderators' => array(),
2756
						'moderator_groups' => array()
2757
					);
2758
				}
2759
				// If a moderator exists for this board, add that moderator for all children too.
2760 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...
2761
					foreach ($boards as $id => $dummy)
2762
					{
2763
						$boards[$id]['moderators'][$row['id_moderator']] = array(
2764
							'id' => $row['id_moderator'],
2765
							'name' => $row['real_name'],
2766
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
2767
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
2768
						);
2769
					}
2770
2771
				// If a moderator group exists for this board, add that moderator group for all children too
2772 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...
2773
					foreach ($boards as $id => $dummy)
2774
					{
2775
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
2776
							'id' => $row['id_moderator_group'],
2777
							'name' => $row['group_name'],
2778
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
2779
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
2780
						);
2781
					}
2782
			}
2783
			$smcFunc['db_free_result']($result);
2784
		}
2785
2786
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
2787
	}
2788
2789
	return $boards;
2790
}
2791
2792
/**
2793
 * Attempt to reload our known languages.
2794
 * It will try to choose only utf8 or non-utf8 languages.
2795
 *
2796
 * @param bool $use_cache Whether or not to use the cache
2797
 * @return array An array of information about available languages
2798
 */
2799
function getLanguages($use_cache = true)
2800
{
2801
	global $context, $smcFunc, $settings, $modSettings;
2802
2803
	// Either we don't use the cache, or its expired.
2804
	if (!$use_cache || ($context['languages'] = cache_get_data('known_languages', !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600)) == null)
2805
	{
2806
		// If we don't have our ucwords function defined yet, let's load the settings data.
2807
		if (empty($smcFunc['ucwords']))
2808
			reloadSettings();
2809
2810
		// If we don't have our theme information yet, let's get it.
2811
		if (empty($settings['default_theme_dir']))
2812
			loadTheme(0, false);
2813
2814
		// Default language directories to try.
2815
		$language_directories = array(
2816
			$settings['default_theme_dir'] . '/languages',
2817
		);
2818
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
2819
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
2820
2821
		// We possibly have a base theme directory.
2822
		if (!empty($settings['base_theme_dir']))
2823
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
2824
2825
		// Remove any duplicates.
2826
		$language_directories = array_unique($language_directories);
2827
2828
		// Get a list of languages.
2829
		$langList = !empty($modSettings['langList']) ? json_decode($modSettings['langList'], true) : array();
2830
		$langList = is_array($langList) ? $langList : false;
2831
2832
		$catchLang = array();
2833
2834
		foreach ($language_directories as $language_dir)
2835
		{
2836
			// Can't look in here... doesn't exist!
2837
			if (!file_exists($language_dir))
2838
				continue;
2839
2840
			$dir = dir($language_dir);
2841
			while ($entry = $dir->read())
2842
			{
2843
				// Look for the index language file... For good measure skip any "index.language-utf8.php" files
2844
				if (!preg_match('~^index\.(.+[^-utf8])\.php$~', $entry, $matches))
2845
					continue;
2846
2847
				if (!empty($langList) && !empty($langList[$matches[1]]))
2848
					$langName = $langList[$matches[1]];
2849
2850
				else
2851
				{
2852
					$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
2853
2854
					// Get the line we need.
2855
					$fp = @fopen($language_dir . '/' . $entry);
2856
2857
					// Yay!
2858
					if ($fp)
2859
					{
2860
						while (($line = fgets($fp)) !== false)
2861
						{
2862
							preg_match('~\$txt\[\'native_name\'\] = \'(.+)\'\;~', $line, $matchNative);
2863
2864
							// Set the language's name.
2865
							if (!empty($matchNative) && !empty($matchNative[1]))
2866
							{
2867
								$langName = un_htmlspecialchars($matchNative[1]);
2868
								break;
2869
							}
2870
						}
2871
2872
						fclose($fp);
2873
					}
2874
2875
					// Catch the language name.
2876
					$catchLang[$matches[1]] = $langName;
2877
				}
2878
2879
				// Build this language entry.
2880
				$context['languages'][$matches[1]] = array(
2881
					'name' => $langName,
2882
					'selected' => false,
2883
					'filename' => $matches[1],
2884
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
2885
				);
2886
			}
2887
			$dir->close();
2888
		}
2889
2890
		// Do we need to store the lang list?
2891
		if (empty($langList))
2892
			updateSettings(array('langList' => json_encode($catchLang)));
2893
2894
		// Let's cash in on this deal.
2895 View Code Duplication
		if (!empty($modSettings['cache_enable']))
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...
2896
			cache_put_data('known_languages', $context['languages'], !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
2897
	}
2898
2899
	return $context['languages'];
2900
}
2901
2902
/**
2903
 * Replace all vulgar words with respective proper words. (substring or whole words..)
2904
 * What this function does:
2905
 *  - it censors the passed string.
2906
 *  - if the theme setting allow_no_censored is on, and the theme option
2907
 *	show_no_censored is enabled, does not censor, unless force is also set.
2908
 *  - it caches the list of censored words to reduce parsing.
2909
 *
2910
 * @param string &$text The text to censor
2911
 * @param bool $force Whether to censor the text regardless of settings
2912
 * @return string The censored text
2913
 */
2914
function censorText(&$text, $force = false)
2915
{
2916
	global $modSettings, $options, $txt;
2917
	static $censor_vulgar = null, $censor_proper;
2918
2919
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
2920
		return $text;
2921
2922
	// If they haven't yet been loaded, load them.
2923
	if ($censor_vulgar == null)
2924
	{
2925
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
2926
		$censor_proper = explode("\n", $modSettings['censor_proper']);
2927
2928
		// Quote them for use in regular expressions.
2929
		if (!empty($modSettings['censorWholeWord']))
2930
		{
2931
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
2932
			{
2933
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
2934
				$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' : '');
2935
2936
				// @todo I'm thinking the old way is some kind of bug and this is actually fixing it.
2937
				//if (strpos($censor_vulgar[$i], '\'') !== false)
2938
					//$censor_vulgar[$i] = str_replace('\'', '&#039;', $censor_vulgar[$i]);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2939
			}
2940
		}
2941
	}
2942
2943
	// Censoring isn't so very complicated :P.
2944
	if (empty($modSettings['censorWholeWord']))
2945
	{
2946
		$func = !empty($modSettings['censorIgnoreCase']) ? 'str_ireplace' : 'str_replace';
2947
		$text = $func($censor_vulgar, $censor_proper, $text);
2948
	}
2949
	else
2950
		$text = preg_replace($censor_vulgar, $censor_proper, $text);
2951
2952
	return $text;
2953
}
2954
2955
/**
2956
 * Load the template/language file using eval or require? (with eval we can show an error message!)
2957
 * 	- loads the template or language file specified by filename.
2958
 * 	- uses eval unless disableTemplateEval is enabled.
2959
 * 	- outputs a parse error if the file did not exist or contained errors.
2960
 * 	- attempts to detect the error and line, and show detailed information.
2961
 *
2962
 * @param string $filename The name of the file to include
2963
 * @param bool $once If true only includes the file once (like include_once)
2964
 */
2965
function template_include($filename, $once = false)
2966
{
2967
	global $context, $settings, $txt, $scripturl, $modSettings;
2968
	global $boardurl, $boarddir, $sourcedir;
2969
	global $maintenance, $mtitle, $mmessage;
2970
	static $templates = array();
2971
2972
	// We want to be able to figure out any errors...
2973
	@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...
2974
2975
	// Don't include the file more than once, if $once is true.
2976
	if ($once && in_array($filename, $templates))
2977
		return;
2978
	// Add this file to the include list, whether $once is true or not.
2979
	else
2980
		$templates[] = $filename;
2981
2982
	// Are we going to use eval?
2983
	if (empty($modSettings['disableTemplateEval']))
2984
	{
2985
		$file_found = file_exists($filename) && eval('?' . '>' . rtrim(file_get_contents($filename))) !== false;
0 ignored issues
show
Coding Style introduced by
The function template_include() contains an eval expression.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
2986
		$settings['current_include_filename'] = $filename;
2987
	}
2988
	else
2989
	{
2990
		$file_found = file_exists($filename);
2991
2992
		if ($once && $file_found)
2993
			require_once($filename);
2994
		elseif ($file_found)
2995
			require($filename);
2996
	}
2997
2998
	if ($file_found !== true)
2999
	{
3000
		ob_end_clean();
3001
		if (!empty($modSettings['enableCompressedOutput']))
3002
			@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...
3003
		else
3004
			ob_start();
3005
3006 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...
3007
			header('Content-Type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3008
3009
		// Don't cache error pages!!
3010
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
3011
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3012
		header('Cache-Control: no-cache');
3013
3014
		if (!isset($txt['template_parse_error']))
3015
		{
3016
			$txt['template_parse_error'] = 'Template Parse Error!';
3017
			$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>.';
3018
			$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>.';
3019
			$txt['template_parse_errmsg'] = 'Unfortunately more information is not available at this time as to exactly what is wrong.';
3020
		}
3021
3022
		// First, let's get the doctype and language information out of the way.
3023
		echo '<!DOCTYPE html>
3024
<html', !empty($context['right_to_left']) ? ' dir="rtl"' : '', '>
3025
	<head>';
3026
		if (isset($context['character_set']))
3027
			echo '
3028
		<meta charset="', $context['character_set'], '">';
3029
3030
		if (!empty($maintenance) && !allowedTo('admin_forum'))
3031
			echo '
3032
		<title>', $mtitle, '</title>
3033
	</head>
3034
	<body>
3035
		<h3>', $mtitle, '</h3>
3036
		', $mmessage, '
3037
	</body>
3038
</html>';
3039
		elseif (!allowedTo('admin_forum'))
3040
			echo '
3041
		<title>', $txt['template_parse_error'], '</title>
3042
	</head>
3043
	<body>
3044
		<h3>', $txt['template_parse_error'], '</h3>
3045
		', $txt['template_parse_error_message'], '
3046
	</body>
3047
</html>';
3048
		else
3049
		{
3050
			require_once($sourcedir . '/Subs-Package.php');
3051
3052
			$error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3053
			$error_array = error_get_last();
3054
			if (empty($error) && ini_get('track_errors') && !empty($error_array))
3055
				$error = $error_array['message'];
3056
			if (empty($error))
3057
				$error = $txt['template_parse_errmsg'];
3058
3059
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
3060
3061
			echo '
3062
		<title>', $txt['template_parse_error'], '</title>
3063
	</head>
3064
	<body>
3065
		<h3>', $txt['template_parse_error'], '</h3>
3066
		', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3067
3068
			if (!empty($error))
3069
				echo '
3070
		<hr>
3071
3072
		<div style="margin: 0 20px;"><pre>', strtr(strtr($error, array('<strong>' . $boarddir => '<strong>...', '<strong>' . strtr($boarddir, '\\', '/') => '<strong>...')), '\\', '/'), '</pre></div>';
3073
3074
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
3075
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
3076
			{
3077
				$data = file($filename);
3078
				$data2 = highlight_php_code(implode('', $data));
3079
				$data2 = preg_split('~\<br( /)?\>~', $data2);
3080
3081
				// Fix the PHP code stuff...
3082
				if (!isBrowser('gecko'))
3083
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
3084
				else
3085
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
3086
3087
				// Now we get to work around a bug in PHP where it doesn't escape <br>s!
3088
				$j = -1;
3089
				foreach ($data as $line)
3090
				{
3091
					$j++;
3092
3093
					if (substr_count($line, '<br>') == 0)
3094
						continue;
3095
3096
					$n = substr_count($line, '<br>');
3097
					for ($i = 0; $i < $n; $i++)
3098
					{
3099
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
3100
						unset($data2[$j + $i + 1]);
3101
					}
3102
					$j += $n;
3103
				}
3104
				$data2 = array_values($data2);
3105
				array_unshift($data2, '');
3106
3107
				echo '
3108
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
3109
3110
				// Figure out what the color coding was before...
3111
				$line = max($match[1] - 9, 1);
3112
				$last_line = '';
3113
				for ($line2 = $line - 1; $line2 > 1; $line2--)
3114
					if (strpos($data2[$line2], '<') !== false)
3115
					{
3116
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
3117
							$last_line = $color_match[1];
3118
						break;
3119
					}
3120
3121
				// Show the relevant lines...
3122
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
3123
				{
3124
					if ($line == $match[1])
3125
						echo '</pre><div style="background-color: #ffb0b5;"><pre style="margin: 0;">';
3126
3127
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
3128
					if (isset($data2[$line]) && $data2[$line] != '')
3129
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
3130
3131
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
3132
					{
3133
						$last_line = $color_match[1];
3134
						echo '</', substr($last_line, 1, 4), '>';
3135
					}
3136
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
3137
						$last_line = '';
3138
					elseif ($last_line != '' && $data2[$line] != '')
3139
						echo '</', substr($last_line, 1, 4), '>';
3140
3141
					if ($line == $match[1])
3142
						echo '</pre></div><pre style="margin: 0;">';
3143
					else
3144
						echo "\n";
3145
				}
3146
3147
				echo '</pre></div>';
3148
			}
3149
3150
			echo '
3151
	</body>
3152
</html>';
3153
		}
3154
3155
		die;
3156
	}
3157
}
3158
3159
/**
3160
 * Initialize a database connection.
3161
 */
3162
function loadDatabase()
3163
{
3164
	global $db_persist, $db_connection, $db_server, $db_user, $db_passwd;
3165
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $sourcedir, $db_prefix, $db_port;
3166
3167
	// Figure out what type of database we are using.
3168
	if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
3169
		$db_type = 'mysql';
3170
3171
	// Load the file for the database.
3172
	require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
3173
3174
	$db_options = array();
3175
3176
	// Add in the port if needed
3177
	if (!empty($db_port))
3178
		$db_options['port'] = $db_port;
3179
3180
	// 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.
3181
	if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
3182
	{
3183
		$options = array_merge($db_options, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true));
3184
3185
		$db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, $options);
3186
	}
3187
3188
	// Either we aren't in SSI mode, or it failed.
3189
	if (empty($db_connection))
3190
	{
3191
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
3192
3193
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
3194
	}
3195
3196
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
3197
	if (!$db_connection)
3198
		display_db_error();
3199
3200
	// If in SSI mode fix up the prefix.
3201
	if (SMF == 'SSI')
3202
		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...
3203
}
3204
3205
/**
3206
 * Try to load up a supported caching method. This is saved in $cacheAPI if we are not overriding it.
3207
 *
3208
 * @param string $overrideCache Try to use a different cache method other than that defined in $cache_accelerator.
3209
 * @param string $fallbackSMF Use the default SMF method if the accelerator fails.
3210
 * @return object A object of $cacheAPI.
3211
*/
3212
function loadCacheAccelerator($overrideCache = null, $fallbackSMF = true)
3213
{
3214
	global $sourcedir, $cacheAPI, $cache_accelerator;
3215
3216
	// Not overriding this and we have a cacheAPI, send it back.
3217
	if (empty($overrideCache) && is_object($cacheAPI))
3218
		return $cacheAPI;
3219
	elseif (is_null($cacheAPI))
3220
		$cacheAPI = false;
3221
3222
	// Make sure our class is in session.
3223
	require_once($sourcedir . '/Class-CacheAPI.php');
3224
3225
	// What accelerator we are going to try.
3226
	$tryAccelerator = !empty($overrideCache) ? $overrideCache : !empty($cache_accelerator) ? $cache_accelerator : 'smf';
3227
	$tryAccelerator = strtolower($tryAccelerator);
3228
3229
	// Do some basic tests.
3230
	if (file_exists($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php'))
3231
	{
3232
		require_once($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php');
3233
3234
		$cache_class_name = $tryAccelerator . '_cache';
3235
		$testAPI = new $cache_class_name();
3236
3237
		// No Support?  NEXT!
3238
		if (!$testAPI->isSupported())
3239
		{
3240
			// Can we save ourselves?
3241
			if (!empty($fallbackSMF) && is_null($overrideCache) && $tryAccelerator != 'smf')
3242
				return loadCacheAccelerator(null, false);
3243
			return false;
3244
		}
3245
3246
		// Connect up to the accelerator.
3247
		$testAPI->connect();
3248
3249
		// Don't set this if we are overriding the cache.
3250
		if (is_null($overrideCache))
3251
		{
3252
			$cacheAPI = $testAPI;
3253
			return $cacheAPI;
3254
		}
3255
		else
3256
			return $testAPI;
3257
	}
3258
}
3259
3260
/**
3261
 * Try to retrieve a cache entry. On failure, call the appropriate function.
3262
 *
3263
 * @param string $key The key for this entry
3264
 * @param string $file The file associated with this entry
3265
 * @param string $function The function to call
3266
 * @param array $params Parameters to be passed to the specified function
3267
 * @param int $level The cache level
3268
 * @return string The cached data
3269
 */
3270
function cache_quick_get($key, $file, $function, $params, $level = 1)
3271
{
3272
	global $modSettings, $sourcedir;
3273
3274
	// @todo Why are we doing this if caching is disabled?
3275
3276
	if (function_exists('call_integration_hook'))
3277
		call_integration_hook('pre_cache_quick_get', array(&$key, &$file, &$function, &$params, &$level));
3278
3279
	/* Refresh the cache if either:
3280
		1. Caching is disabled.
3281
		2. The cache level isn't high enough.
3282
		3. The item has not been cached or the cached item expired.
3283
		4. The cached item has a custom expiration condition evaluating to true.
3284
		5. The expire time set in the cache item has passed (needed for Zend).
3285
	*/
3286
	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()))
0 ignored issues
show
Coding Style introduced by
The function cache_quick_get() contains an eval expression.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
3287
	{
3288
		require_once($sourcedir . '/' . $file);
3289
		$cache_block = call_user_func_array($function, $params);
3290
3291
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= $level)
3292
			cache_put_data($key, $cache_block, $cache_block['expires'] - time());
3293
	}
3294
3295
	// Some cached data may need a freshening up after retrieval.
3296
	if (!empty($cache_block['post_retri_eval']))
3297
		eval($cache_block['post_retri_eval']);
0 ignored issues
show
Coding Style introduced by
The function cache_quick_get() contains an eval expression.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
3298
3299
	if (function_exists('call_integration_hook'))
3300
		call_integration_hook('post_cache_quick_get', array(&$cache_block));
3301
3302
	return $cache_block['data'];
3303
}
3304
3305
/**
3306
 * Puts value in the cache under key for ttl seconds.
3307
 *
3308
 * - It may "miss" so shouldn't be depended on
3309
 * - Uses the cache engine chosen in the ACP and saved in settings.php
3310
 * - It supports:
3311
 *	 Xcache: https://xcache.lighttpd.net/wiki/XcacheApi
3312
 *	 memcache: https://php.net/memcache
3313
 *	 APC: https://php.net/apc
3314
 *   APCu: https://php.net/book.apcu
3315
 *	 Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
3316
 *	 Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
3317
 *
3318
 * @param string $key A key for this value
3319
 * @param mixed $value The data to cache
3320
 * @param int $ttl How long (in seconds) the data should be cached for
3321
 */
3322
function cache_put_data($key, $value, $ttl = 120)
3323
{
3324
	global $cache_enable, $cacheAPI;
3325
	global $cache_hits, $cache_count, $db_show_debug;
3326
3327
	if (empty($cache_enable) || empty($cacheAPI))
3328
		return;
3329
3330
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3331
	if (isset($db_show_debug) && $db_show_debug === true)
3332
	{
3333
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'put', 's' => $value === null ? 0 : strlen(json_encode($value)));
3334
		$st = microtime();
3335
	}
3336
3337
	// The API will handle the rest.
3338
	$value = $value === null ? null : json_encode($value);
3339
	$cacheAPI->putData($key, $value, $ttl);
3340
3341
	if (function_exists('call_integration_hook'))
3342
		call_integration_hook('cache_put_data', array(&$key, &$value, &$ttl));
3343
3344 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...
3345
		$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...
3346
}
3347
3348
/**
3349
 * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
3350
 * - It may often "miss", so shouldn't be depended on.
3351
 * - It supports the same as cache_put_data().
3352
 *
3353
 * @param string $key The key for the value to retrieve
3354
 * @param int $ttl The maximum age of the cached data
3355
 * @return string The cached data or null if nothing was loaded
3356
 */
3357
function cache_get_data($key, $ttl = 120)
3358
{
3359
	global $cache_enable, $cacheAPI;
3360
	global $cache_hits, $cache_count, $cache_misses, $cache_count_misses, $db_show_debug;
3361
3362
	if (empty($cache_enable) || empty($cacheAPI))
3363
		return;
3364
3365
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3366
	if (isset($db_show_debug) && $db_show_debug === true)
3367
	{
3368
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'get');
3369
		$st = microtime();
3370
		$original_key = $key;
3371
	}
3372
3373
	// Ask the API to get the data.
3374
	$value = $cacheAPI->getData($key, $ttl);
3375
3376
	if (isset($db_show_debug) && $db_show_debug === true)
3377
	{
3378
		$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...
3379
		$cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0;
3380
3381
		if (empty($value))
3382
		{
3383
			if (!is_array($cache_misses))
3384
				$cache_misses = array();
3385
3386
			$cache_count_misses = isset($cache_count_misses) ? $cache_count_misses + 1 : 1;
3387
			$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...
3388
		}
3389
	}
3390
3391
	if (function_exists('call_integration_hook') && isset($value))
3392
		call_integration_hook('cache_get_data', array(&$key, &$ttl, &$value));
3393
3394
	return empty($value) ? null : smf_json_decode($value, true);
3395
}
3396
3397
/**
3398
 * Empty out the cache in use as best it can
3399
 *
3400
 * It may only remove the files of a certain type (if the $type parameter is given)
3401
 * Type can be user, data or left blank
3402
 * 	- user clears out user data
3403
 *  - data clears out system / opcode data
3404
 *  - If no type is specified will perform a complete cache clearing
3405
 * For cache engines that do not distinguish on types, a full cache flush will be done
3406
 *
3407
 * @param string $type The cache type ('memcached', 'apc', 'xcache', 'zend' or something else for SMF's file cache)
3408
 */
3409
function clean_cache($type = '')
3410
{
3411
	global $cacheAPI;
3412
3413
	// If we can't get to the API, can't do this.
3414
	if (empty($cacheAPI))
3415
		return;
3416
3417
	// Ask the API to do the heavy lifting. cleanCache also calls invalidateCache to be sure.
3418
	$cacheAPI->cleanCache($type);
3419
3420
	call_integration_hook('integrate_clean_cache');
3421
	clearstatcache();
3422
}
3423
3424
/**
3425
 * Helper function to set an array of data for an user's avatar.
3426
 *
3427
 * Makes assumptions based on the data provided, the following keys are required:
3428
 * - avatar The raw "avatar" column in members table
3429
 * - email The user's email. Used to get the gravatar info
3430
 * - filename The attachment filename
3431
 *
3432
 * @param array $data An array of raw info
3433
 * @return array An array of avatar data
3434
 */
3435
function set_avatar_data($data = array())
3436
{
3437
	global $modSettings, $boardurl, $smcFunc, $image_proxy_enabled, $image_proxy_secret;
3438
3439
	// Come on!
3440
	if (empty($data))
3441
		return array();
3442
3443
	// Set a nice default var.
3444
	$image = '';
3445
3446
	// Gravatar has been set as mandatory!
3447
	if (!empty($modSettings['gravatarOverride']))
3448
	{
3449
		if (!empty($modSettings['gravatarAllowExtraEmail']) && !empty($data['avatar']) && stristr($data['avatar'], 'gravatar://'))
3450
			$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3451
3452
		else if (!empty($data['email']))
3453
			$image = get_gravatar_url($data['email']);
3454
	}
3455
3456
	// Look if the user has a gravatar field or has set an external url as avatar.
3457
	else
3458
	{
3459
		// So it's stored in the member table?
3460
		if (!empty($data['avatar']))
3461
		{
3462
			// Gravatar.
3463
			if (stristr($data['avatar'], 'gravatar://'))
3464
			{
3465
				if ($data['avatar'] == 'gravatar://')
3466
					$image = get_gravatar_url($data['email']);
3467
3468
				elseif (!empty($modSettings['gravatarAllowExtraEmail']))
3469
					$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3470
			}
3471
3472
			// External url.
3473
			else
3474
			{
3475
				// Using ssl?
3476
				if (!empty($modSettings['force_ssl']) && $image_proxy_enabled && stripos($data['avatar'], 'http://') !== false)
3477
					$image = strtr($boardurl, array('http://' => 'https://')) . '/proxy.php?request=' . urlencode($data['avatar']) . '&hash=' . md5($data['avatar'] . $image_proxy_secret);
3478
3479
				// Just a plain external url.
3480
				else
3481
					$image = (stristr($data['avatar'], 'http://') || stristr($data['avatar'], 'https://')) ? $data['avatar'] : $modSettings['avatar_url'] . '/' . $data['avatar'];
3482
			}
3483
		}
3484
3485
		// Perhaps this user has an attachment as avatar...
3486
		else if (!empty($data['filename']))
3487
			$image = $modSettings['custom_avatar_url'] . '/' . $data['filename'];
3488
3489
		// Right... no avatar... use our default image.
3490
		else
3491
			$image = $modSettings['avatar_url'] . '/default.png';
3492
	}
3493
3494
	call_integration_hook('integrate_set_avatar_data', array(&$image, &$data));
3495
3496
	// 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.
3497
	if (!empty($image))
3498
		return array(
3499
			'name' => !empty($data['avatar']) ? $data['avatar'] : '',
3500
			'image' => '<img class="avatar" src="' . $image . '" />',
3501
			'href' => $image,
3502
			'url' => $image,
3503
		);
3504
3505
	// Fallback to make life easier for everyone...
3506
	else
3507
		return array(
3508
			'name' => '',
3509
			'image' => '',
3510
			'href' => '',
3511
			'url' => '',
3512
		);
3513
}
3514
3515
?>
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...