Completed
Pull Request — release-2.1 (#6024)
by John
15:33
created

loadUserInfo()   F

Complexity

Conditions 31
Paths > 20000

Size

Total Lines 51
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 31
eloc 43
c 2
b 0
f 0
nc 805306368
nop 2
dl 0
loc 51
rs 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 https://www.simplemachines.org
10
 * @copyright 2020 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 RC2
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, $forum_version, $boardurl;
26
	global $image_proxy_enabled;
27
28
	// Most database systems have not set UTF-8 as their default input charset.
29
	if (!empty($db_character_set))
30
		$smcFunc['db_query']('', '
31
			SET NAMES {string:db_character_set}',
32
			array(
33
				'db_character_set' => $db_character_set,
34
			)
35
		);
36
37
	// We need some caching support, maybe.
38
	loadCacheAccelerator();
39
40
	// Try to load it from the cache first; it'll never get cached if the setting is off.
41
	if (($modSettings = cache_get_data('modSettings', 90)) == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $modSettings = cache_get_data('modSettings', 90) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
42
	{
43
		$request = $smcFunc['db_query']('', '
44
			SELECT variable, value
45
			FROM {db_prefix}settings',
46
			array(
47
			)
48
		);
49
		$modSettings = array();
50
		if (!$request)
51
			display_db_error();
52
		foreach ($smcFunc['db_fetch_all']($request) as $row)
53
			$modSettings[$row['variable']] = $row['value'];
54
		$smcFunc['db_free_result']($request);
55
56
		// Do a few things to protect against missing settings or settings with invalid values...
57
		if (empty($modSettings['defaultMaxTopics']) || $modSettings['defaultMaxTopics'] <= 0 || $modSettings['defaultMaxTopics'] > 999)
58
			$modSettings['defaultMaxTopics'] = 20;
59
		if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999)
60
			$modSettings['defaultMaxMessages'] = 15;
61
		if (empty($modSettings['defaultMaxMembers']) || $modSettings['defaultMaxMembers'] <= 0 || $modSettings['defaultMaxMembers'] > 999)
62
			$modSettings['defaultMaxMembers'] = 30;
63
		if (empty($modSettings['defaultMaxListItems']) || $modSettings['defaultMaxListItems'] <= 0 || $modSettings['defaultMaxListItems'] > 999)
64
			$modSettings['defaultMaxListItems'] = 15;
65
66
		// We explicitly do not use $smcFunc['json_decode'] here yet, as $smcFunc is not fully loaded.
67
		if (!is_array($modSettings['attachmentUploadDir']))
68
		{
69
			$attachmentUploadDir = smf_json_decode($modSettings['attachmentUploadDir'], true);
70
			$modSettings['attachmentUploadDir'] = !empty($attachmentUploadDir) ? $attachmentUploadDir : $modSettings['attachmentUploadDir'];
71
		}
72
73
		if (!empty($cache_enable))
74
			cache_put_data('modSettings', $modSettings, 90);
75
	}
76
77
	// Going anything further when the files don't match the database can make nasty messes (unless we're actively installing or upgrading)
78
	if (!defined('SMF_INSTALLING') && (!isset($_REQUEST['action']) || $_REQUEST['action'] !== 'admin' || !isset($_REQUEST['area']) || $_REQUEST['area'] !== 'packages') && !empty($modSettings['smfVersion']) && version_compare(strtolower(strtr($modSettings['smfVersion'], array(' ' => '.'))), strtolower(strtr(SMF_VERSION, array(' ' => '.'))), '!='))
79
	{
80
		// Wipe the cached $modSettings values so they don't interfere with anything later
81
		cache_put_data('modSettings', null);
82
83
		// Redirect to the upgrader if we can
84
		if (file_exists($boarddir . '/upgrade.php'))
85
			header('location: ' . $boardurl . '/upgrade.php');
86
87
		die('SMF file version (' . SMF_VERSION . ') does not match SMF database version (' . $modSettings['smfVersion'] . ').<br>Run the SMF upgrader to fix this.<br><a href="https://wiki.simplemachines.org/smf/Upgrading">More information</a>.');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
88
	}
89
90
	$modSettings['cache_enable'] = $cache_enable;
91
92
	// Used to force browsers to download fresh CSS and JavaScript when necessary
93
	$modSettings['browser_cache'] = !empty($modSettings['browser_cache']) ? (int) $modSettings['browser_cache'] : 0;
94
	$context['browser_cache'] = '?' . preg_replace('~\W~', '', strtolower(SMF_FULL_VERSION)) . '_' . $modSettings['browser_cache'];
95
96
	// Disable image proxy if we don't have SSL enabled
97
	if (empty($modSettings['force_ssl']))
98
		$image_proxy_enabled = false;
99
100
	// UTF-8 ?
101
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
102
	$context['utf8'] = $utf8;
103
104
	// Set a list of common functions.
105
	$ent_list = '&(?:#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . '|quot|amp|lt|gt|nbsp);';
106
	$ent_check = empty($modSettings['disableEntityCheck']) ? function($string)
107
	{
108
		$string = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $string);
109
110
		return $string;
111
	} : function($string)
112
	{
113
		return $string;
114
	};
115
	$fix_utf8mb4 = function($string) use ($utf8, $smcFunc)
116
	{
117
		if (!$utf8 || $smcFunc['db_mb4'])
118
			return $string;
119
120
		$i = 0;
121
		$len = strlen($string);
122
		$new_string = '';
123
		while ($i < $len)
124
		{
125
			$ord = ord($string[$i]);
126
			if ($ord < 128)
127
			{
128
				$new_string .= $string[$i];
129
				$i++;
130
			}
131
			elseif ($ord < 224)
132
			{
133
				$new_string .= $string[$i] . $string[$i + 1];
134
				$i += 2;
135
			}
136
			elseif ($ord < 240)
137
			{
138
				$new_string .= $string[$i] . $string[$i + 1] . $string[$i + 2];
139
				$i += 3;
140
			}
141
			elseif ($ord < 248)
142
			{
143
				// Magic happens.
144
				$val = (ord($string[$i]) & 0x07) << 18;
145
				$val += (ord($string[$i + 1]) & 0x3F) << 12;
146
				$val += (ord($string[$i + 2]) & 0x3F) << 6;
147
				$val += (ord($string[$i + 3]) & 0x3F);
148
				$new_string .= '&#' . $val . ';';
149
				$i += 4;
150
			}
151
		}
152
		return $new_string;
153
	};
154
155
	// global array of anonymous helper functions, used mostly to properly handle multi byte strings
156
	$smcFunc += array(
157
		'entity_fix' => function($string)
158
		{
159
			$num = $string[0] === 'x' ? hexdec(substr($string, 1)) : (int) $string;
160
			return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? '' : '&#' . $num . ';';
161
		},
162
		'htmlspecialchars' => function($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') use ($ent_check, $utf8, $fix_utf8mb4)
163
		{
164
			return $fix_utf8mb4($ent_check(htmlspecialchars($string, $quote_style, $utf8 ? 'UTF-8' : $charset)));
165
		},
166
		'htmltrim' => function($string) use ($utf8, $ent_check)
167
		{
168
			// Preg_replace space characters depend on the character set in use
169
			$space_chars = $utf8 ? '\p{Z}\p{C}' : '\x00-\x20\x80-\xA0';
170
171
			return preg_replace('~^(?:[' . $space_chars . ']|&nbsp;)+|(?:[' . $space_chars . ']|&nbsp;)+$~' . ($utf8 ? 'u' : ''), '', $ent_check($string));
172
		},
173
		'strlen' => function($string) use ($ent_list, $utf8, $ent_check)
174
		{
175
			return strlen(preg_replace('~' . $ent_list . ($utf8 ? '|.~u' : '~'), '_', $ent_check($string)));
176
		},
177
		'strpos' => function($haystack, $needle, $offset = 0) use ($utf8, $ent_check, $ent_list, $modSettings)
0 ignored issues
show
Unused Code introduced by
The import $modSettings is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
178
		{
179
			$haystack_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : ''), $ent_check($haystack), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
180
181
			if (strlen($needle) === 1)
182
			{
183
				$result = array_search($needle, array_slice($haystack_arr, $offset));
0 ignored issues
show
Bug introduced by
It seems like $haystack_arr can also be of type false; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

183
				$result = array_search($needle, array_slice(/** @scrutinizer ignore-type */ $haystack_arr, $offset));
Loading history...
184
				return is_int($result) ? $result + $offset : false;
185
			}
186
			else
187
			{
188
				$needle_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($needle), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
189
				$needle_size = count($needle_arr);
0 ignored issues
show
Bug introduced by
It seems like $needle_arr can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

189
				$needle_size = count(/** @scrutinizer ignore-type */ $needle_arr);
Loading history...
190
191
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
192
				while ((int) $result === $result)
193
				{
194
					$offset += $result;
195
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
196
						return $offset;
197
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
198
				}
199
				return false;
200
			}
201
		},
202
		'substr' => function($string, $start, $length = null) use ($utf8, $ent_check, $ent_list, $modSettings)
0 ignored issues
show
Unused Code introduced by
The import $modSettings is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
203
		{
204
			$ent_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($string), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
205
			return $length === null ? implode('', array_slice($ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
0 ignored issues
show
Bug introduced by
It seems like $ent_arr can also be of type false; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

205
			return $length === null ? implode('', array_slice(/** @scrutinizer ignore-type */ $ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
Loading history...
206
		},
207
		'strtolower' => $utf8 ? function($string) use ($sourcedir)
208
		{
209
			if (!function_exists('mb_strtolower'))
210
			{
211
				require_once($sourcedir . '/Subs-Charset.php');
212
				return utf8_strtolower($string);
213
			}
214
215
			return mb_strtolower($string, 'UTF-8');
216
		} : 'strtolower',
217
		'strtoupper' => $utf8 ? function($string)
218
		{
219
			global $sourcedir;
220
221
			if (!function_exists('mb_strtolower'))
222
			{
223
				require_once($sourcedir . '/Subs-Charset.php');
224
				return utf8_strtoupper($string);
225
			}
226
227
			return mb_strtoupper($string, 'UTF-8');
228
		} : 'strtoupper',
229
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
230
		{
231
			$string = $ent_check($string);
232
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~' . ($utf8 ? 'u' : ''), $string, $matches);
233
			$string = $matches[0];
234
			while (strlen($string) > $length)
235
				$string = preg_replace('~(?:' . $ent_list . '|.)$~' . ($utf8 ? 'u' : ''), '', $string);
236
			return $string;
237
		},
238
		'ucfirst' => $utf8 ? function($string) use (&$smcFunc)
239
		{
240
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
241
		} : 'ucfirst',
242
		'ucwords' => $utf8 ? function($string) use (&$smcFunc)
243
		{
244
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
245
			for ($i = 0, $n = count($words); $i < $n; $i += 2)
0 ignored issues
show
Bug introduced by
It seems like $words can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

245
			for ($i = 0, $n = count(/** @scrutinizer ignore-type */ $words); $i < $n; $i += 2)
Loading history...
246
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
247
			return implode('', $words);
0 ignored issues
show
Bug introduced by
It seems like $words can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

247
			return implode('', /** @scrutinizer ignore-type */ $words);
Loading history...
248
		} : 'ucwords',
249
		'json_decode' => 'smf_json_decode',
250
		'json_encode' => 'json_encode',
251
		'random_int' => function($min = 0, $max = PHP_INT_MAX)
252
		{
253
			global $sourcedir;
254
255
			// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
256
			if (!is_callable('random_int'))
257
				require_once($sourcedir . '/random_compat/random.php');
258
259
			return random_int($min, $max);
260
		},
261
		'random_bytes' => function($length = 64)
262
		{
263
			global $sourcedir;
264
265
			if (!is_callable('random_bytes'))
266
				require_once($sourcedir . '/random_compat/random.php');
267
268
			// Make sure length is valid
269
			$length = max(1, (int) $length);
270
271
			return random_bytes($length);
272
		},
273
	);
274
275
	// Setting the timezone is a requirement for some functions.
276
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], timezone_identifiers_list()))
0 ignored issues
show
Bug introduced by
It seems like timezone_identifiers_list() can also be of type false; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

276
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], /** @scrutinizer ignore-type */ timezone_identifiers_list()))
Loading history...
277
		date_default_timezone_set($modSettings['default_timezone']);
278
	else
279
	{
280
		// Get PHP's default timezone, if set
281
		$ini_tz = ini_get('date.timezone');
282
		if (!empty($ini_tz))
283
			$modSettings['default_timezone'] = $ini_tz;
284
		else
285
			$modSettings['default_timezone'] = '';
286
287
		// If date.timezone is unset, invalid, or just plain weird, make a best guess
288
		if (!in_array($modSettings['default_timezone'], timezone_identifiers_list()))
289
		{
290
			$server_offset = @mktime(0, 0, 0, 1, 1, 1970);
291
			$modSettings['default_timezone'] = timezone_name_from_abbr('', $server_offset, 0);
292
		}
293
294
		date_default_timezone_set($modSettings['default_timezone']);
295
	}
296
297
	// Check the load averages?
298
	if (!empty($modSettings['loadavg_enable']))
299
	{
300
		if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $modSettings['load_avera...get_data('loadavg', 90) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
301
		{
302
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
303
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
304
				$modSettings['load_average'] = (float) $matches[1];
305
			elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0)
306
				$modSettings['load_average'] = (float) $matches[1];
307
			else
308
				unset($modSettings['load_average']);
309
310
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
311
				cache_put_data('loadavg', $modSettings['load_average'], 90);
312
		}
313
314
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
315
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
316
317
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
318
			display_loadavg_error();
319
	}
320
321
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
322
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
323
324
	// Here to justify the name of this function. :P
325
	// It should be added to the install and upgrade scripts.
326
	// But since the converters need to be updated also. This is easier.
327
	if (empty($modSettings['currentAttachmentUploadDir']))
328
	{
329
		updateSettings(array(
330
			'attachmentUploadDir' => $smcFunc['json_encode'](array(1 => $modSettings['attachmentUploadDir'])),
331
			'currentAttachmentUploadDir' => 1,
332
		));
333
	}
334
335
	// Respect PHP's limits.
336
	$post_max_kb = floor(memoryReturnBytes(ini_get('post_max_size')) / 1024);
337
	$file_max_kb = floor(memoryReturnBytes(ini_get('upload_max_filesize')) / 1024);
338
	$modSettings['attachmentPostLimit'] = empty($modSettings['attachmentPostLimit']) ? $post_max_kb : min($modSettings['attachmentPostLimit'], $post_max_kb);
339
	$modSettings['attachmentSizeLimit'] = empty($modSettings['attachmentSizeLimit']) ? $file_max_kb : min($modSettings['attachmentSizeLimit'], $file_max_kb);
340
341
	// Integration is cool.
342
	if (defined('SMF_INTEGRATION_SETTINGS'))
343
	{
344
		$integration_settings = $smcFunc['json_decode'](SMF_INTEGRATION_SETTINGS, true);
0 ignored issues
show
Bug introduced by
The constant SMF_INTEGRATION_SETTINGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
345
		foreach ($integration_settings as $hook => $function)
346
			add_integration_function($hook, $function, false);
347
	}
348
349
	// Any files to pre include?
350
	if (!empty($modSettings['integrate_pre_include']))
351
	{
352
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
353
		foreach ($pre_includes as $include)
354
		{
355
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
356
			if (file_exists($include))
357
				require_once($include);
358
		}
359
	}
360
361
	// This determines the server... not used in many places, except for login fixing.
362
	$context['server'] = array(
363
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
364
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
365
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
366
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
367
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
368
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
369
		'is_windows' => DIRECTORY_SEPARATOR === '\\',
370
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
371
	);
372
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
373
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
374
375
	// Define a list of icons used across multiple places.
376
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
377
378
	// Define an array for custom profile fields placements.
379
	$context['cust_profile_fields_placement'] = array(
380
		'standard',
381
		'icons',
382
		'above_signature',
383
		'below_signature',
384
		'below_avatar',
385
		'above_member',
386
		'bottom_poster',
387
		'before_member',
388
		'after_member',
389
	);
390
391
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
392
	$context['meta_tags'] = array();
393
394
	// Define an array of allowed HTML tags.
395
	$context['allowed_html_tags'] = array(
396
		'<img>',
397
		'<div>',
398
	);
399
400
	// These are the only valid image types for SMF attachments, by default anyway.
401
	// Note: The values are for image mime types, not file extensions.
402
	$context['valid_image_types'] = array(
403
		IMAGETYPE_GIF => 'gif',
404
		IMAGETYPE_JPEG => 'jpeg',
405
		IMAGETYPE_PNG => 'png',
406
		IMAGETYPE_PSD => 'psd',
407
		IMAGETYPE_BMP => 'bmp',
408
		IMAGETYPE_TIFF_II => 'tiff',
409
		IMAGETYPE_TIFF_MM => 'tiff',
410
		IMAGETYPE_IFF => 'iff'
411
	);
412
413
	// Define a list of allowed tags for descriptions.
414
	$context['description_allowed_tags'] = array(
415
		'abbr', 'anchor', 'b', 'center', 'color', 'font', 'hr', 'i', 'img',
416
		'iurl', 'left', 'li', 'list', 'ltr', 'pre', 'right', 's', 'sub',
417
		'sup', 'table', 'td', 'tr', 'u', 'url',
418
	);
419
420
	// Define a list of deprecated BBC tags
421
	// Even when enabled, they'll only work in old posts and not new ones
422
	$context['legacy_bbc'] = array(
423
		'acronym', 'bdo', 'black', 'blue', 'flash', 'ftp', 'glow',
424
		'green', 'move', 'red', 'shadow', 'tt', 'white',
425
	);
426
427
	// Define a list of BBC tags that require permissions to use
428
	$context['restricted_bbc'] = array(
429
		'html',
430
	);
431
432
	// Call pre load integration functions.
433
	call_integration_hook('integrate_pre_load');
434
}
435
436
/**
437
 * Loads the user general details, including username, id, groups, and a few more things.
438
 *
439
 * - Check integrate_verify_user for a user id first, then the cookie.
440
 * - Having established the user id, proceed to load the current member's
441
 *   data (from cache as appropriate) and make sure it is cached.
442
 * - Store the member data in $user_settings, ensure that it is
443
 *   cached and verify that the password is right.
444
 * - Check whether the user is attempting to flood the login with
445
 *   requests, and deal with it as appropriate.
446
 * - Assuming the member is correct, check and update the
447
 *   last-visit information if appropriate.
448
 * - Ensure the user groups are sanitized.
449
 * - Perform 'is this a spider' checks.
450
 * - Check their two factor authentication status, and require it where appropriate.
451
 * - Populate $user_info with lots of useful information about the
452
 *   user (id, username, email, password, language, whether they are
453
 *   a guest or admin, post count, IP address, time format/offset,
454
 *   avatar, smileys, PM counts, buddy list, ignore user/board
455
 *   preferences, warning level, and user groups).
456
 * - Establish board access rights based as an SQL clause (based
457
 *   on user groups) in $user_info['query_see_board'], and a
458
 *   subset of this to include ignore boards preferences
459
 *   into $user_info['query_wanna_see_board'].
460
 */
461
function loadUserSettings()
462
{
463
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
464
	global $cookiename, $user_info, $language, $context, $cache_enable;
465
466
	require_once($sourcedir . '/Subs-Auth.php');
467
468
	// Check first the integration, then the cookie, and last the session.
469
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
470
	{
471
		$id_member = 0;
472
		foreach ($integration_ids as $integration_id)
473
		{
474
			$integration_id = (int) $integration_id;
475
			if ($integration_id > 0)
476
			{
477
				$id_member = $integration_id;
478
				$already_verified = true;
479
				break;
480
			}
481
		}
482
	}
483
	else
484
		$id_member = 0;
485
486
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
487
	{
488
		// First try 2.1 json-format cookie
489
		$cookie_data = $smcFunc['json_decode']($_COOKIE[$cookiename], true, false);
490
491
		// Legacy format (for recent 2.0 --> 2.1 upgrades)
492
		if (empty($cookie_data))
493
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
494
495
		list($id_member, $password, $login_span, $cookie_domain, $cookie_path) = array_pad((array) $cookie_data, 5, '');
496
497
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
498
499
		// Make sure the cookie is set to the correct domain and path
500
		if (array($cookie_domain, $cookie_path) !== url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])))
501
			setLoginCookie((int) $login_span - time(), $id_member);
502
	}
503
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
504
	{
505
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
506
		$cookie_data = $smcFunc['json_decode']($_SESSION['login_' . $cookiename], true);
507
508
		if (empty($cookie_data))
509
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
510
511
		list($id_member, $password, $login_span) = array_pad((array) $cookie_data, 3, '');
512
		$id_member = !empty($id_member) && strlen($password) == 40 && (int) $login_span > time() ? (int) $id_member : 0;
513
	}
514
515
	// Only load this stuff if the user isn't a guest.
516
	if ($id_member != 0)
517
	{
518
		// Is the member data cached?
519
		if (empty($cache_enable) || $cache_enable < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $user_settings = cache_g...ngs-' . $id_member, 60) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
520
		{
521
			$request = $smcFunc['db_query']('', '
522
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
523
				FROM {db_prefix}members AS mem
524
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
525
				WHERE mem.id_member = {int:id_member}
526
				LIMIT 1',
527
				array(
528
					'id_member' => $id_member,
529
				)
530
			);
531
			$user_settings = $smcFunc['db_fetch_assoc']($request);
532
			$smcFunc['db_free_result']($request);
533
534
			$user_settings['groups'] = array_merge(
535
				array($user_settings['id_group'], $user_settings['id_post_group']),
536
				explode(',', $user_settings['additional_groups'])
537
			);
538
539
			// Figure out the new time offset.
540
			if (!empty($user_settings['timezone']))
541
			{
542
				// Get the offsets from UTC for the server, then for the user.
543
				$tz_system = new DateTimeZone(@date_default_timezone_get());
544
				$tz_user = new DateTimeZone($user_settings['timezone']);
545
				$time_system = new DateTime('now', $tz_system);
546
				$time_user = new DateTime('now', $tz_user);
547
				$user_settings['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
548
			}
549
550
			if (!empty($cache_enable) && $cache_enable >= 2)
551
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
552
		}
553
554
		// Did we find 'im?  If not, junk it.
555
		if (!empty($user_settings))
556
		{
557
			// As much as the password should be right, we can assume the integration set things up.
558
			if (!empty($already_verified) && $already_verified === true)
559
				$check = true;
560
			// SHA-512 hash should be 128 characters long.
561
			elseif (strlen($password) == 128)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $password does not seem to be defined for all execution paths leading up to this point.
Loading history...
562
				$check = hash_equals(hash_salt($user_settings['passwd'], $user_settings['password_salt']), $password);
563
			else
564
				$check = false;
565
566
			// Wrong password or not activated - either way, you're going nowhere.
567
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
568
		}
569
		else
570
			$id_member = 0;
571
572
		// Check if we are forcing TFA
573
		$force_tfasetup = !empty($modSettings['tfa_mode']) && $modSettings['tfa_mode'] >= 2 && $id_member && empty($user_settings['tfa_secret']) && SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != '.xml');
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
574
575
		// Don't force TFA on popups
576
		if ($force_tfasetup)
0 ignored issues
show
introduced by
The condition $force_tfasetup is always false.
Loading history...
577
		{
578
			if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'profile' && isset($_REQUEST['area']) && in_array($_REQUEST['area'], array('popup', 'alerts_popup')))
579
				$force_tfasetup = false;
580
			elseif (isset($_REQUEST['action']) && $_REQUEST['action'] == 'pm' && (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'popup'))
581
				$force_tfasetup = false;
582
583
			call_integration_hook('integrate_force_tfasetup', array(&$force_tfasetup));
584
		}
585
586
		// If we no longer have the member maybe they're being all hackey, stop brute force!
587
		if (!$id_member)
588
		{
589
			require_once($sourcedir . '/LogInOut.php');
590
			validatePasswordFlood(
591
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
592
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
593
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
594
				$id_member != 0
595
			);
596
		}
597
		// Validate for Two Factor Authentication
598
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
599
		{
600
			$tfacookie = $cookiename . '_tfa';
601
			$tfasecret = null;
602
603
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
604
605
			if (empty($verified) || !in_array(true, $verified))
606
			{
607
				if (!empty($_COOKIE[$tfacookie]))
608
				{
609
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie], true);
610
611
					list ($tfamember, $tfasecret) = array_pad((array) $tfa_data, 2, '');
612
613
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
614
						$tfasecret = null;
615
				}
616
617
				// They didn't finish logging in before coming here? Then they're no one to us.
618
				if (empty($tfasecret) || !hash_equals(hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']), $tfasecret))
619
				{
620
					setLoginCookie(-3600, $id_member);
621
					$id_member = 0;
622
					$user_settings = array();
623
				}
624
			}
625
		}
626
		// When authenticating their two factor code, make sure to reset their ID for security
627
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
628
		{
629
			$id_member = 0;
630
			$context['tfa_member'] = $user_settings;
631
			$user_settings = array();
632
		}
633
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
634
		elseif ($force_tfasetup)
0 ignored issues
show
introduced by
The condition $force_tfasetup is always false.
Loading history...
635
		{
636
			$total = 1;
637
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
638
			{
639
				//Build an array of ALL user membergroups.
640
				$full_groups = array($user_settings['id_group']);
641
				if (!empty($user_settings['additional_groups']))
642
				{
643
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
644
					$full_groups = array_unique($full_groups); //duplicates, maybe?
645
				}
646
647
				//Find out if any group requires 2FA
648
				$request = $smcFunc['db_query']('', '
649
					SELECT COUNT(id_group) AS total
650
					FROM {db_prefix}membergroups
651
					WHERE tfa_required = {int:tfa_required}
652
						AND id_group IN ({array_int:full_groups})',
653
					array(
654
						'tfa_required' => 1,
655
						'full_groups' => $full_groups,
656
					)
657
				);
658
				list ($total) = $smcFunc['db_fetch_row']($request);
659
				$smcFunc['db_free_result']($request);
660
			}
661
662
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
663
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
664
665
			if ($total > 0 && !in_array($action, array('profile', 'logout')) || ($action == 'profile' && $area != 'tfasetup'))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($total > 0 && ! in_arra... && $area != 'tfasetup', Probably Intended Meaning: $total > 0 && (! in_arra...&& $area != 'tfasetup')
Loading history...
666
				redirectexit('action=profile;area=tfasetup;forced');
667
		}
668
	}
669
670
	// If the user is a guest, initialize all the critical user settings.
671
	if ($id_member == 0)
672
	{
673
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
674
			$_COOKIE[$cookiename] = '';
675
676
		// Expire the 2FA cookie
677
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
678
		{
679
			$tfa_data = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
680
681
			list (,, $exp) = array_pad((array) $tfa_data, 3, 0);
682
683
			if (time() > $exp)
684
			{
685
				$_COOKIE[$cookiename . '_tfa'] = '';
686
				setTFACookie(-3600, 0, '');
687
			}
688
		}
689
690
		// Create a login token if it doesn't exist yet.
691
		if (!isset($_SESSION['token']['post-login']))
692
			createToken('login');
693
		else
694
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
695
696
		// Login Cookie times. Format: time => txt
697
		$context['login_cookie_times'] = array(
698
			3153600 => 'always_logged_in',
699
			60 => 'one_hour',
700
			1440 => 'one_day',
701
			10080 => 'one_week',
702
			43200 => 'one_month',
703
		);
704
705
		$user_settings = array();
706
	}
707
708
	// Found 'im, let's set up the variables.
709
	updateLastLogin($id_member, $user_settings);
710
	$user_info = loadUserInfo($id_member, $user_settings);
711
	$user_info['possibly_robot'] = loadRobotInfo($id_member);
712
713
	// Allow the user to change their language.
714
	if (!empty($modSettings['userLanguage']))
715
	{
716
		$languages = getLanguages();
717
718
		// Is it valid?
719
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
720
		{
721
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
722
723
			// Make it permanent for members.
724
			if (!empty($user_info['id']))
725
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
726
			else
727
				$_SESSION['language'] = $user_info['language'];
728
		}
729
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
730
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
731
	}
732
}
733
734
/**
735
 * Update the last visit time based on the time of their last message.
736
 *
737
 * - Don't do this while in SSI or if the user is still trying to log in.
738
 * - RSS feeds and XMLHTTP requests don't count either.
739
 * - Only update this if their last message was made at least 5 hours ago.
740
 *
741
 * @param int $id_member
742
 * @param array $user_settings
743
 */
744
function updateLastLogin($id_member, $user_settings)
745
{
746
	global $cache_enable, $smcFunc;
747
748
	if ($id_member != 0)
749
	{
750
		// Let's not update the last visit time in these cases...
751
		// 1. SSI doesn't count as visiting the forum.
752
		// 2. RSS feeds and XMLHTTP requests don't count either.
753
		// 3. If it was set within this session, no need to set it again.
754
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
755
		// 5. We're still logging in or authenticating
756
		if (SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || !in_array($_REQUEST['action'], array('.xml', 'login2', 'logintfa'))) && empty($_SESSION['id_msg_last_visit']) && (empty($cache_enable) || ($_SESSION['id_msg_last_visit'] = cache_get_data('user_last_visit-' . $id_member, 5 * 3600)) === null))
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
757
		{
758
			// @todo can this be cached?
759
			// Do a quick query to make sure this isn't a mistake.
760
			$result = $smcFunc['db_query']('', '
761
				SELECT poster_time
762
				FROM {db_prefix}messages
763
				WHERE id_msg = {int:id_msg}
764
				LIMIT 1',
765
				array(
766
					'id_msg' => $user_settings['id_msg_last_visit'],
767
				)
768
			);
769
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
770
			$smcFunc['db_free_result']($result);
771
772
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
773
774
			// If it was *at least* five hours ago...
775
			if ($visitTime < time() - 5 * 3600)
776
			{
777
				updateMemberData(
778
					$id_member,
779
					array(
780
						'id_msg_last_visit' => (int) $modSettings['maxMsgID'],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $modSettings seems to be never defined.
Loading history...
781
						'last_login' => time(),
782
						'member_ip' => $_SERVER['REMOTE_ADDR'],
783
						'member_ip2' => $_SERVER['BAN_CHECK_IP'],
784
					)
785
				);
786
				$user_settings['last_login'] = time();
787
788
				if (!empty($cache_enable) && $cache_enable >= 2)
789
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
790
791
				if (!empty($cache_enable))
792
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
793
			}
794
		}
795
		elseif (empty($_SESSION['id_msg_last_visit']))
796
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
797
	}
798
}
799
800
/**
801
 * Finds out if this is a spider or not.
802
 *
803
 * @param int $id_member
804
 *
805
 * @return bool|int
806
 */
807
function loadRobotInfo($id_member)
808
{
809
	global $modSettings, $sourcedir;
810
811
	$possibly_robot = false;
812
	if ($id_member == 0)
813
	{
814
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
815
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
816
		{
817
			require_once($sourcedir . '/ManageSearchEngines.php');
818
			$possibly_robot = SpiderCheck();
819
		}
820
		elseif (!empty($modSettings['spider_mode']))
821
			$possibly_robot = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
822
		// If we haven't turned on proper spider hunts then have a guess!
823
		else
824
		{
825
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
826
			$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, 'bingbot') !== false || strpos($ci_user_agent, 'bingpreview') !== false || strpos($ci_user_agent, 'adidxbot') !== false || strpos($ci_user_agent, 'msnbot') !== false;
827
		}
828
	}
829
830
	return $possibly_robot;
831
}
832
833
/**
834
 * Returns an array suitable for populating `$user_info` with.
835
 *
836
 * @param int $id_member
837
 * @param array $user_settings
838
 *
839
 * @return array
840
 */
841
function loadUserInfo($id_member, $user_settings)
842
{
843
	global $language, $modSettings;
844
845
	$user_info = array(
846
		'id' => $id_member,
847
		'username' => isset($user_settings['member_name']) ? $user_settings['member_name'] : '',
848
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
849
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
850
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
851
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
852
		'is_guest' => $id_member == 0,
853
		'groups' => isset($user_settings['groups']) ? array_map('intval', $user_settings['groups']) : [-1],
854
		'is_admin' => isset($user_settings['groups']) && in_array(1, $user_settings['groups']),
855
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
856
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
857
		'ip' => $_SERVER['REMOTE_ADDR'],
858
		'ip2' => $_SERVER['BAN_CHECK_IP'],
859
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
860
		'time_offset' => empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'],
861
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
862
		'avatar' => array(
863
			'url' => isset($user_settings['avatar']) ? get_proxied_url($user_settings['avatar']) : '',
864
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
865
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
866
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
867
		),
868
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
869
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
870
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
871
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
872
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
873
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
874
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? array_filter(explode(',', $user_settings['ignore_boards'])) : array(),
875
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? array_filter(explode(',', $user_settings['pm_ignore_list'])) : array(),
876
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
877
		'permissions' => array(),
878
	);
879
	$user_info['can_manage_boards'] = !empty($user_info['is_admin']) || (!empty($modSettings['board_manager_groups']) && count(array_intersect($user_info['groups'], explode(',', $modSettings['board_manager_groups']))) > 0);
880
881
	$temp = build_query_board($user_info['id']);
882
	$user_info['query_see_board'] = $temp['query_see_board'];
883
	$user_info['query_see_message_board'] = $temp['query_see_message_board'];
884
	$user_info['query_see_topic_board'] = $temp['query_see_topic_board'];
885
	$user_info['query_wanna_see_board'] = $temp['query_wanna_see_board'];
886
	$user_info['query_wanna_see_message_board'] = $temp['query_wanna_see_message_board'];
887
	$user_info['query_wanna_see_topic_board'] = $temp['query_wanna_see_topic_board'];
888
889
	call_integration_hook('integrate_user_info');
890
891
	return $user_info;
892
}
893
894
/**
895
 * Validate whether we are dealing with a board, and whether
896
 * the current user has access to that board.
897
 *
898
 * - sets up the $board_info array for current board information.
899
 * - if cache is enabled, the $board_info array is stored in cache.
900
 * - redirects to appropriate post if only message id is requested.
901
 * - is only used when inside a topic or board.
902
 * - determines the local moderators for the board.
903
 * - adds group id 3 if the user is a local moderator for the board they are in.
904
 * - prevents access if user is not in proper group nor a local moderator of the board.
905
 */
906
function loadBoard()
907
{
908
	global $txt, $scripturl, $context, $modSettings;
909
	global $board_info, $board, $topic, $user_info, $smcFunc, $cache_enable;
910
911
	// Assume they are not a moderator.
912
	$user_info['is_mod'] = false;
913
	$context['user']['is_mod'] = &$user_info['is_mod'];
914
915
	// Start the linktree off empty..
916
	$context['linktree'] = array();
917
918
	// Have they by chance specified a message id but nothing else?
919
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
920
	{
921
		// Make sure the message id is really an int.
922
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
923
924
		// Looking through the message table can be slow, so try using the cache first.
925
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
926
		{
927
			$request = $smcFunc['db_query']('', '
928
				SELECT id_topic
929
				FROM {db_prefix}messages
930
				WHERE id_msg = {int:id_msg}
931
				LIMIT 1',
932
				array(
933
					'id_msg' => $_REQUEST['msg'],
934
				)
935
			);
936
937
			// So did it find anything?
938
			if ($smcFunc['db_num_rows']($request))
939
			{
940
				list ($topic) = $smcFunc['db_fetch_row']($request);
941
				$smcFunc['db_free_result']($request);
942
				// Save save save.
943
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
944
			}
945
		}
946
947
		// Remember redirection is the key to avoiding fallout from your bosses.
948
		if (!empty($topic))
949
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
950
		else
951
		{
952
			loadPermissions();
953
			loadTheme();
954
			fatal_lang_error('topic_gone', false);
955
		}
956
	}
957
958
	// Load this board only if it is specified.
959
	if (empty($board) && empty($topic))
960
	{
961
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
962
		return;
963
	}
964
965
	if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
966
	{
967
		// @todo SLOW?
968
		if (!empty($topic))
969
			$temp = cache_get_data('topic_board-' . $topic, 120);
970
		else
971
			$temp = cache_get_data('board-' . $board, 120);
972
973
		if (!empty($temp))
974
		{
975
			$board_info = $temp;
976
			$board = $board_info['id'];
977
		}
978
	}
979
980
	if (empty($temp))
981
	{
982
		$custom_column_selects = [];
983
		$custom_column_parameters = [
984
			'current_topic' => $topic,
985
			'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
986
		];
987
988
		call_integration_hook('integrate_load_board', array(&$custom_column_selects, &$custom_column_parameters));
989
990
		$request = $smcFunc['db_query']('load_board_info', '
991
			SELECT
992
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
993
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
994
				COALESCE(mem.id_member, 0) AS id_moderator,
995
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
996
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
997
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
998
				' . (!empty($custom_column_selects) ? (', ' . implode(', ', $custom_column_selects)) : '') . '
999
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
1000
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
1001
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1002
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
1003
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
1004
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
1005
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
1006
			WHERE b.id_board = {raw:board_link}',
1007
			$custom_column_parameters
1008
		);
1009
1010
		// If there aren't any, skip.
1011
		if ($smcFunc['db_num_rows']($request) > 0)
1012
		{
1013
			$row = $smcFunc['db_fetch_assoc']($request);
1014
1015
			// Set the current board.
1016
			if (!empty($row['id_board']))
1017
				$board = $row['id_board'];
1018
1019
			// Basic operating information. (globals... :/)
1020
			$board_info = array(
1021
				'id' => $board,
1022
				'moderators' => array(),
1023
				'moderator_groups' => array(),
1024
				'cat' => array(
1025
					'id' => $row['id_cat'],
1026
					'name' => $row['cname']
1027
				),
1028
				'name' => $row['bname'],
1029
				'description' => $row['description'],
1030
				'num_topics' => $row['num_topics'],
1031
				'unapproved_topics' => $row['unapproved_topics'],
1032
				'unapproved_posts' => $row['unapproved_posts'],
1033
				'unapproved_user_topics' => 0,
1034
				'parent_boards' => getBoardParents($row['id_parent']),
1035
				'parent' => $row['id_parent'],
1036
				'child_level' => $row['child_level'],
1037
				'theme' => $row['id_theme'],
1038
				'override_theme' => !empty($row['override_theme']),
1039
				'profile' => $row['id_profile'],
1040
				'redirect' => $row['redirect'],
1041
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
1042
				'posts_count' => empty($row['count_posts']),
1043
				'cur_topic_approved' => empty($topic) || $row['approved'],
1044
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
1045
			);
1046
1047
			// Load the membergroups allowed, and check permissions.
1048
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
1049
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
1050
1051
			call_integration_hook('integrate_board_info', array(&$board_info, $row));
1052
1053
			if (!empty($modSettings['board_manager_groups']))
1054
			{
1055
				$board_info['groups'] = array_unique(array_merge($board_info['groups'], explode(',', $modSettings['board_manager_groups'])));
1056
				$board_info['deny_groups'] = array_diff($board_info['deny_groups'], explode(',', $modSettings['board_manager_groups']));
1057
			}
1058
1059
			do
1060
			{
1061
				if (!empty($row['id_moderator']))
1062
					$board_info['moderators'][$row['id_moderator']] = array(
1063
						'id' => $row['id_moderator'],
1064
						'name' => $row['real_name'],
1065
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
1066
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
1067
					);
1068
1069
				if (!empty($row['id_moderator_group']))
1070
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
1071
						'id' => $row['id_moderator_group'],
1072
						'name' => $row['group_name'],
1073
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
1074
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
1075
					);
1076
			}
1077
			while ($row = $smcFunc['db_fetch_assoc']($request));
1078
1079
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
1080
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
1081
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
1082
			{
1083
				// Free the previous result
1084
				$smcFunc['db_free_result']($request);
1085
1086
				// @todo why is this using id_topic?
1087
				// @todo Can this get cached?
1088
				$request = $smcFunc['db_query']('', '
1089
					SELECT COUNT(id_topic)
1090
					FROM {db_prefix}topics
1091
					WHERE id_member_started={int:id_member}
1092
						AND approved = {int:unapproved}
1093
						AND id_board = {int:board}',
1094
					array(
1095
						'id_member' => $user_info['id'],
1096
						'unapproved' => 0,
1097
						'board' => $board,
1098
					)
1099
				);
1100
1101
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
1102
			}
1103
1104
			if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1105
			{
1106
				// @todo SLOW?
1107
				if (!empty($topic))
1108
					cache_put_data('topic_board-' . $topic, $board_info, 120);
1109
				cache_put_data('board-' . $board, $board_info, 120);
1110
			}
1111
		}
1112
		else
1113
		{
1114
			// Otherwise the topic is invalid, there are no moderators, etc.
1115
			$board_info = array(
1116
				'moderators' => array(),
1117
				'moderator_groups' => array(),
1118
				'error' => 'exist'
1119
			);
1120
			$topic = null;
1121
			$board = 0;
1122
		}
1123
		$smcFunc['db_free_result']($request);
1124
	}
1125
1126
	if (!empty($topic))
1127
		$_GET['board'] = (int) $board;
1128
1129
	if (!empty($board))
1130
	{
1131
		// Get this into an array of keys for array_intersect
1132
		$moderator_groups = array_keys($board_info['moderator_groups']);
1133
1134
		// Now check if the user is a moderator.
1135
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
1136
1137
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
1138
			$board_info['error'] = 'access';
1139
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
1140
			$board_info['error'] = 'access';
1141
1142
		// Build up the linktree.
1143
		$context['linktree'] = array_merge(
1144
			$context['linktree'],
1145
			array(array(
1146
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
1147
				'name' => $board_info['cat']['name']
1148
			)),
1149
			array_reverse($board_info['parent_boards']),
1150
			array(array(
1151
				'url' => $scripturl . '?board=' . $board . '.0',
1152
				'name' => $board_info['name']
1153
			))
1154
		);
1155
	}
1156
1157
	// Set the template contextual information.
1158
	$context['user']['is_mod'] = &$user_info['is_mod'];
1159
	$context['current_topic'] = $topic;
1160
	$context['current_board'] = $board;
1161
1162
	// No posting in redirection boards!
1163
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1164
		$board_info['error'] = 'post_in_redirect';
1165
1166
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1167
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1168
	{
1169
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1170
		loadPermissions();
1171
		loadTheme();
1172
1173
		$_GET['board'] = '';
1174
		$_GET['topic'] = '';
1175
1176
		// The linktree should not give the game away mate!
1177
		$context['linktree'] = array(
1178
			array(
1179
				'url' => $scripturl,
1180
				'name' => $context['forum_name_html_safe']
1181
			)
1182
		);
1183
1184
		// If it's a prefetching agent or we're requesting an attachment.
1185
		if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach'))
1186
		{
1187
			ob_end_clean();
1188
			send_http_status(403);
1189
			die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
1190
		}
1191
		elseif ($board_info['error'] == 'post_in_redirect')
1192
		{
1193
			// Slightly different error message here...
1194
			fatal_lang_error('cannot_post_redirect', false);
1195
		}
1196
		elseif ($user_info['is_guest'])
1197
		{
1198
			loadLanguage('Errors');
1199
			is_not_guest($txt['topic_gone']);
1200
		}
1201
		else
1202
			fatal_lang_error('topic_gone', false);
1203
	}
1204
1205
	if ($user_info['is_mod'])
1206
		$user_info['groups'][] = 3;
1207
}
1208
1209
/**
1210
 * Load this user's permissions.
1211
 */
1212
function loadPermissions()
1213
{
1214
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir, $cache_enable;
1215
1216
	if ($user_info['is_admin'])
1217
	{
1218
		banPermissions();
1219
		return;
1220
	}
1221
1222
	if (!empty($cache_enable))
1223
	{
1224
		$cache_groups = $user_info['groups'];
1225
		asort($cache_groups);
1226
		$cache_groups = implode(',', $cache_groups);
1227
		// If it's a spider then cache it different.
1228
		if ($user_info['possibly_robot'])
1229
			$cache_groups .= '-spider';
1230
1231
		if ($cache_enable >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp = cache_get_data('...ps . ':' . $board, 240) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1232
		{
1233
			list ($user_info['permissions']) = $temp;
1234
			banPermissions();
1235
1236
			return;
1237
		}
1238
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp = cache_get_data('...' . $cache_groups, 240) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1239
			list ($user_info['permissions'], $removals) = $temp;
1240
	}
1241
1242
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1243
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1244
1245
	if (empty($user_info['permissions']))
1246
	{
1247
		// Get the general permissions.
1248
		$request = $smcFunc['db_query']('', '
1249
			SELECT permission, add_deny
1250
			FROM {db_prefix}permissions
1251
			WHERE id_group IN ({array_int:member_groups})
1252
				' . $spider_restrict,
1253
			array(
1254
				'member_groups' => $user_info['groups'],
1255
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1256
			)
1257
		);
1258
		$removals = array();
1259
		while ($row = $smcFunc['db_fetch_assoc']($request))
1260
		{
1261
			if (empty($row['add_deny']))
1262
				$removals[] = $row['permission'];
1263
			else
1264
				$user_info['permissions'][] = $row['permission'];
1265
		}
1266
		$smcFunc['db_free_result']($request);
1267
1268
		if (isset($cache_groups))
1269
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1270
	}
1271
1272
	// Get the board permissions.
1273
	if (!empty($board))
1274
	{
1275
		// Make sure the board (if any) has been loaded by loadBoard().
1276
		if (!isset($board_info['profile']))
1277
			fatal_lang_error('no_board');
1278
1279
		$request = $smcFunc['db_query']('', '
1280
			SELECT permission, add_deny
1281
			FROM {db_prefix}board_permissions
1282
			WHERE (id_group IN ({array_int:member_groups})
1283
				' . $spider_restrict . ')
1284
				AND id_profile = {int:id_profile}',
1285
			array(
1286
				'member_groups' => $user_info['groups'],
1287
				'id_profile' => $board_info['profile'],
1288
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1289
			)
1290
		);
1291
		while ($row = $smcFunc['db_fetch_assoc']($request))
1292
		{
1293
			if (empty($row['add_deny']))
1294
				$removals[] = $row['permission'];
1295
			else
1296
				$user_info['permissions'][] = $row['permission'];
1297
		}
1298
		$smcFunc['db_free_result']($request);
1299
	}
1300
1301
	// Remove all the permissions they shouldn't have ;).
1302
	if (!empty($modSettings['permission_enable_deny']))
1303
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $removals does not seem to be defined for all execution paths leading up to this point.
Loading history...
1304
1305
	if (isset($cache_groups) && !empty($board) && $cache_enable >= 2)
1306
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1307
1308
	// Banned?  Watch, don't touch..
1309
	banPermissions();
1310
1311
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1312
	if (!$user_info['is_guest'])
1313
	{
1314
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1315
		{
1316
			require_once($sourcedir . '/Subs-Auth.php');
1317
			rebuildModCache();
1318
		}
1319
		else
1320
			$user_info['mod_cache'] = $_SESSION['mc'];
1321
1322
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1323
		// For example this drastically simplifies certain changes to the profile area.
1324
		$user_info['permissions'][] = 'is_not_guest';
1325
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1326
		$user_info['permissions'][] = 'profile_view_own';
1327
		if (in_array('profile_view', $user_info['permissions']))
1328
			$user_info['permissions'][] = 'profile_view_any';
1329
	}
1330
}
1331
1332
/**
1333
 * Loads an array of users' data by ID or member_name.
1334
 *
1335
 * @param array|string $users An array of users by id or name or a single username/id
1336
 * @param bool $is_name Whether $users contains names
1337
 * @param string $set What kind of data to load (normal, profile, minimal)
1338
 * @return array The ids of the members loaded
1339
 */
1340
function loadMemberData($users, $is_name = false, $set = 'normal')
1341
{
1342
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1343
	global $user_info, $cache_enable;
1344
1345
	// Can't just look for no users :P.
1346
	if (empty($users))
1347
		return array();
1348
1349
	// Pass the set value
1350
	$context['loadMemberContext_set'] = $set;
1351
1352
	// Make sure it's an array.
1353
	$users = !is_array($users) ? array($users) : array_unique($users);
1354
	$loaded_ids = array();
1355
1356
	if (!$is_name && !empty($cache_enable) && $cache_enable >= 3)
1357
	{
1358
		$users = array_values($users);
1359
		for ($i = 0, $n = count($users); $i < $n; $i++)
1360
		{
1361
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1362
			if ($data == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $data of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1363
				continue;
1364
1365
			$loaded_ids[] = $data['id_member'];
1366
			$user_profile[$data['id_member']] = $data;
1367
			unset($users[$i]);
1368
		}
1369
	}
1370
1371
	// Used by default
1372
	$select_columns = '
1373
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1374
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1375
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1376
			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.timezone, mem.show_online,
1377
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1378
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1379
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1380
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1381
	$select_tables = '
1382
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1383
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1384
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1385
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1386
1387
	// We add or replace according the the set
1388
	switch ($set)
1389
	{
1390
		case 'normal':
1391
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1392
			break;
1393
		case 'profile':
1394
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1395
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1396
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1397
			break;
1398
		case 'minimal':
1399
			$select_columns = '
1400
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1401
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1402
			$select_tables = '';
1403
			break;
1404
		default:
1405
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1406
	}
1407
1408
	// Allow mods to easily add to the selected member data
1409
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1410
1411
	if (!empty($users))
1412
	{
1413
		// Load the member's data.
1414
		$request = $smcFunc['db_query']('', '
1415
			SELECT' . $select_columns . '
1416
			FROM {db_prefix}members AS mem' . $select_tables . '
1417
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1418
			array(
1419
				'blank_string' => '',
1420
				'users' => $users,
1421
			)
1422
		);
1423
		$new_loaded_ids = array();
1424
		while ($row = $smcFunc['db_fetch_assoc']($request))
1425
		{
1426
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1427
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1428
1429
			// Take care of proxying avatar if required, do this here for maximum reach
1430
			if (!empty($row['avatar']))
1431
				$row['avatar'] = get_proxied_url($row['avatar']);
1432
1433
			// Keep track of the member's normal member group
1434
			$row['primary_group'] = !empty($row['member_group']) ? $row['member_group'] : '';
1435
1436
			if (isset($row['member_ip']))
1437
				$row['member_ip'] = inet_dtop($row['member_ip']);
1438
			if (isset($row['member_ip2']))
1439
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1440
			$row['id_member'] = (int) $row['id_member'];
1441
			$new_loaded_ids[] = $row['id_member'];
1442
			$loaded_ids[] = $row['id_member'];
1443
			$row['options'] = array();
1444
			$user_profile[$row['id_member']] = $row;
1445
		}
1446
		$smcFunc['db_free_result']($request);
1447
	}
1448
1449
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1450
	{
1451
		$request = $smcFunc['db_query']('', '
1452
			SELECT id_member, variable, value
1453
			FROM {db_prefix}themes
1454
			WHERE id_member IN ({array_int:loaded_ids})',
1455
			array(
1456
				'loaded_ids' => $new_loaded_ids,
1457
			)
1458
		);
1459
		while ($row = $smcFunc['db_fetch_assoc']($request))
1460
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1461
		$smcFunc['db_free_result']($request);
1462
	}
1463
1464
	$additional_mods = array();
1465
1466
	// Are any of these users in groups assigned to moderate this board?
1467
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1468
	{
1469
		foreach ($loaded_ids as $a_member)
1470
		{
1471
			if (!empty($user_profile[$a_member]['additional_groups']))
1472
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1473
			else
1474
				$groups = array($user_profile[$a_member]['id_group']);
1475
1476
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1477
1478
			if (!empty($temp))
1479
			{
1480
				$additional_mods[] = $a_member;
1481
			}
1482
		}
1483
	}
1484
1485
	if (!empty($new_loaded_ids) && !empty($cache_enable) && $cache_enable >= 3)
1486
	{
1487
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1488
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1489
	}
1490
1491
	// Are we loading any moderators?  If so, fix their group data...
1492
	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)
1493
	{
1494
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $row = cache_get_data('m...rator_group_info', 480) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1495
		{
1496
			$request = $smcFunc['db_query']('', '
1497
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1498
				FROM {db_prefix}membergroups
1499
				WHERE id_group = {int:moderator_group}
1500
				LIMIT 1',
1501
				array(
1502
					'moderator_group' => 3,
1503
				)
1504
			);
1505
			$row = $smcFunc['db_fetch_assoc']($request);
1506
			$smcFunc['db_free_result']($request);
1507
1508
			cache_put_data('moderator_group_info', $row, 480);
1509
		}
1510
1511
		foreach ($temp_mods as $id)
1512
		{
1513
			// By popular demand, don't show admins or global moderators as moderators.
1514
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1515
				$user_profile[$id]['member_group'] = $row['member_group'];
1516
1517
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1518
			if (!empty($row['icons']))
1519
				$user_profile[$id]['icons'] = $row['icons'];
1520
			if (!empty($row['member_group_color']))
1521
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1522
		}
1523
	}
1524
1525
	return $loaded_ids;
1526
}
1527
1528
/**
1529
 * Loads the user's basic values... meant for template/theme usage.
1530
 *
1531
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1532
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1533
 * @return boolean Whether or not the data was loaded successfully
1534
 */
1535
function loadMemberContext($user, $display_custom_fields = false)
1536
{
1537
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1538
	global $context, $modSettings, $settings, $smcFunc;
1539
	static $dataLoaded = array();
1540
	static $loadedLanguages = array();
1541
1542
	// If this person's data is already loaded, skip it.
1543
	if (isset($dataLoaded[$user]))
1544
		return true;
1545
1546
	// We can't load guests or members not loaded by loadMemberData()!
1547
	if ($user == 0)
1548
		return false;
1549
	if (!isset($user_profile[$user]))
1550
	{
1551
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1552
		return false;
1553
	}
1554
1555
	// Well, it's loaded now anyhow.
1556
	$dataLoaded[$user] = true;
1557
	$profile = $user_profile[$user];
1558
1559
	// Censor everything.
1560
	censorText($profile['signature']);
1561
	censorText($profile['personal_text']);
1562
1563
	// Set things up to be used before hand.
1564
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1565
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1566
1567
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $profile['is_online'] = ...ofile['is_online'] > 0), Probably Intended Meaning: $profile['is_online'] = ...ofile['is_online'] > 0)
Loading history...
1568
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1569
	// Setup the buddy status here (One whole in_array call saved :P)
1570
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1571
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1572
1573
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1574
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1575
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1576
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1577
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1578
	else //not set, bye bye
1579
		$group_icon_url = '';
1580
1581
	// These minimal values are always loaded
1582
	$memberContext[$user] = array(
1583
		'username' => $profile['member_name'],
1584
		'name' => $profile['real_name'],
1585
		'id' => $profile['id_member'],
1586
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1587
		'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>',
1588
		'email' => $profile['email_address'],
1589
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1590
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1591
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1592
	);
1593
1594
	// If the set isn't minimal then load the monstrous array.
1595
	if ($context['loadMemberContext_set'] != 'minimal')
1596
	{
1597
		// Go the extra mile and load the user's native language name.
1598
		if (empty($loadedLanguages))
1599
			$loadedLanguages = getLanguages();
1600
1601
		// Figure out the new time offset.
1602
		if (!empty($profile['timezone']))
1603
		{
1604
			// Get the offsets from UTC for the server, then for the user.
1605
			$tz_system = new DateTimeZone(@date_default_timezone_get());
1606
			$tz_user = new DateTimeZone($profile['timezone']);
1607
			$time_system = new DateTime('now', $tz_system);
1608
			$time_user = new DateTime('now', $tz_user);
1609
			$profile['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
1610
		}
1611
		else
1612
		{
1613
			// !!! Compatibility.
1614
			$profile['time_offset'] = empty($profile['time_offset']) ? 0 : $profile['time_offset'];
1615
		}
1616
1617
		$memberContext[$user] += array(
1618
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1619
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1620
			'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>',
1621
			'is_buddy' => $profile['buddy'],
1622
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1623
			'buddies' => $buddy_list,
1624
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1625
			'blurb' => $profile['personal_text'],
1626
			'website' => array(
1627
				'title' => $profile['website_title'],
1628
				'url' => $profile['website_url'],
1629
			),
1630
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1631
			'signature' => $profile['signature'],
1632
			'real_posts' => $profile['posts'],
1633
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1634
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1635
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
1636
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1637
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1638
			'online' => array(
1639
				'is_online' => $profile['is_online'],
1640
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1641
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1642
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1643
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1644
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1645
			),
1646
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1647
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1648
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1649
			'options' => $profile['options'],
1650
			'is_guest' => false,
1651
			'primary_group' => $profile['primary_group'],
1652
			'group' => $profile['member_group'],
1653
			'group_color' => $profile['member_group_color'],
1654
			'group_id' => $profile['id_group'],
1655
			'post_group' => $profile['post_group'],
1656
			'post_group_color' => $profile['post_group_color'],
1657
			'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]),
1658
			'warning' => $profile['warning'],
1659
			'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' : (''))),
1660
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1661
			'custom_fields' => array(),
1662
		);
1663
	}
1664
1665
	// If the set isn't minimal then load their avatar as well.
1666
	if ($context['loadMemberContext_set'] != 'minimal')
1667
	{
1668
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1669
		{
1670
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1671
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1672
			else
1673
				$image = get_gravatar_url($profile['email_address']);
1674
		}
1675
		else
1676
		{
1677
			// So it's stored in the member table?
1678
			if (!empty($profile['avatar']))
1679
			{
1680
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1681
			}
1682
			elseif (!empty($profile['filename']))
1683
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1684
			// Right... no avatar...use the default one
1685
			else
1686
				$image = $modSettings['avatar_url'] . '/default.png';
1687
		}
1688
		if (!empty($image))
1689
			$memberContext[$user]['avatar'] = array(
1690
				'name' => $profile['avatar'],
1691
				'image' => '<img class="avatar" src="' . $image . '" alt="">',
1692
				'href' => $image,
1693
				'url' => $image,
1694
			);
1695
	}
1696
1697
	// Are we also loading the members custom fields into context?
1698
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1699
	{
1700
		$memberContext[$user]['custom_fields'] = array();
1701
1702
		if (!isset($context['display_fields']))
1703
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1704
1705
		foreach ($context['display_fields'] as $custom)
1706
		{
1707
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1708
				continue;
1709
1710
			$value = $profile['options'][$custom['col_name']];
1711
1712
			$fieldOptions = array();
1713
			$currentKey = 0;
1714
1715
			// Create a key => value array for multiple options fields
1716
			if (!empty($custom['options']))
1717
				foreach ($custom['options'] as $k => $v)
1718
				{
1719
					$fieldOptions[] = $v;
1720
					if (empty($currentKey))
1721
						$currentKey = $v == $value ? $k : 0;
1722
				}
1723
1724
			// BBC?
1725
			if ($custom['bbc'])
1726
				$value = parse_bbc($value);
1727
1728
			// ... or checkbox?
1729
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1730
				$value = $value ? $txt['yes'] : $txt['no'];
1731
1732
			// Enclosing the user input within some other text?
1733
			if (!empty($custom['enclose']))
1734
				$value = strtr($custom['enclose'], array(
1735
					'{SCRIPTURL}' => $scripturl,
1736
					'{IMAGES_URL}' => $settings['images_url'],
1737
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1738
					'{INPUT}' => $value,
1739
					'{KEY}' => $currentKey,
1740
				));
1741
1742
			$memberContext[$user]['custom_fields'][] = array(
1743
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1744
				'col_name' => $custom['col_name'],
1745
				'value' => un_htmlspecialchars($value),
1746
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1747
			);
1748
		}
1749
	}
1750
1751
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1752
	return true;
1753
}
1754
1755
/**
1756
 * Loads the user's custom profile fields
1757
 *
1758
 * @param integer|array $users A single user ID or an array of user IDs
1759
 * @param string|array $params Either a string or an array of strings with profile field names
1760
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1761
 */
1762
function loadMemberCustomFields($users, $params)
1763
{
1764
	global $smcFunc, $txt, $scripturl, $settings;
1765
1766
	// Do not waste my time...
1767
	if (empty($users) || empty($params))
1768
		return false;
1769
1770
	// Make sure it's an array.
1771
	$users = !is_array($users) ? array($users) : array_unique($users);
1772
	$params = !is_array($params) ? array($params) : array_unique($params);
1773
	$return = array();
1774
1775
	$request = $smcFunc['db_query']('', '
1776
		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,
1777
		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
1778
		FROM {db_prefix}themes AS t
1779
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1780
		WHERE id_member IN ({array_int:loaded_ids})
1781
			AND variable IN ({array_string:params})
1782
		ORDER BY field_order',
1783
		array(
1784
			'loaded_ids' => $users,
1785
			'params' => $params,
1786
		)
1787
	);
1788
1789
	while ($row = $smcFunc['db_fetch_assoc']($request))
1790
	{
1791
		$fieldOptions = array();
1792
		$currentKey = 0;
1793
1794
		// Create a key => value array for multiple options fields
1795
		if (!empty($row['field_options']))
1796
			foreach (explode(',', $row['field_options']) as $k => $v)
1797
			{
1798
				$fieldOptions[] = $v;
1799
				if (empty($currentKey))
1800
					$currentKey = $v == $row['value'] ? $k : 0;
1801
			}
1802
1803
		// BBC?
1804
		if (!empty($row['bbc']))
1805
			$row['value'] = parse_bbc($row['value']);
1806
1807
		// ... or checkbox?
1808
		elseif (isset($row['type']) && $row['type'] == 'check')
1809
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1810
1811
		// Enclosing the user input within some other text?
1812
		if (!empty($row['enclose']))
1813
			$row['value'] = strtr($row['enclose'], array(
1814
				'{SCRIPTURL}' => $scripturl,
1815
				'{IMAGES_URL}' => $settings['images_url'],
1816
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1817
				'{INPUT}' => un_htmlspecialchars($row['value']),
1818
				'{KEY}' => $currentKey,
1819
			));
1820
1821
		// Send a simple array if there is just 1 param
1822
		if (count($params) == 1)
1823
			$return[$row['id_member']] = $row;
1824
1825
		// More than 1? knock yourself out...
1826
		else
1827
		{
1828
			if (!isset($return[$row['id_member']]))
1829
				$return[$row['id_member']] = array();
1830
1831
			$return[$row['id_member']][$row['variable']] = $row;
1832
		}
1833
	}
1834
1835
	$smcFunc['db_free_result']($request);
1836
1837
	return !empty($return) ? $return : false;
1838
}
1839
1840
/**
1841
 * Loads information about what browser the user is viewing with and places it in $context
1842
 *  - uses the class from {@link Class-BrowserDetect.php}
1843
 */
1844
function detectBrowser()
1845
{
1846
	// Load the current user's browser of choice
1847
	$detector = new browser_detector;
1848
	$detector->detectBrowser();
1849
}
1850
1851
/**
1852
 * Are we using this browser?
1853
 *
1854
 * Wrapper function for detectBrowser
1855
 *
1856
 * @param string $browser The browser we are checking for.
1857
 * @return bool Whether or not the current browser is what we're looking for
1858
 */
1859
function isBrowser($browser)
1860
{
1861
	global $context;
1862
1863
	// Don't know any browser!
1864
	if (empty($context['browser']))
1865
		detectBrowser();
1866
1867
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1868
}
1869
1870
/**
1871
 * Load a theme, by ID.
1872
 *
1873
 * @param int $id_theme The ID of the theme to load
1874
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1875
 */
1876
function loadTheme($id_theme = 0, $initialize = true)
1877
{
1878
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1879
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1880
	global $context, $settings, $options, $sourcedir, $smcFunc, $language, $board, $cache_enable;
1881
1882
	if (empty($id_theme))
1883
	{
1884
		// The theme was specified by the board.
1885
		if (!empty($board_info['theme']))
1886
			$id_theme = $board_info['theme'];
1887
		// The theme is the forum's default.
1888
		else
1889
			$id_theme = $modSettings['theme_guests'];
1890
1891
		// Sometimes the user can choose their own theme.
1892
		if (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))
1893
		{
1894
			// The theme was specified by REQUEST.
1895
			if (!empty($_REQUEST['theme']) && (allowedTo('admin_forum') || in_array($_REQUEST['theme'], explode(',', $modSettings['knownThemes']))))
1896
			{
1897
				$id_theme = (int) $_REQUEST['theme'];
1898
				$_SESSION['id_theme'] = $id_theme;
1899
			}
1900
			// The theme was specified by REQUEST... previously.
1901
			elseif (!empty($_SESSION['id_theme']))
1902
				$id_theme = (int) $_SESSION['id_theme'];
1903
			// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1904
			elseif (!empty($user_info['theme']))
1905
				$id_theme = $user_info['theme'];
1906
		}
1907
1908
		// Verify the id_theme... no foul play.
1909
		// Always allow the board specific theme, if they are overriding.
1910
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1911
			$id_theme = $board_info['theme'];
1912
		elseif (!empty($modSettings['enableThemes']))
1913
		{
1914
			$themes = explode(',', $modSettings['enableThemes']);
1915
			if (!in_array($id_theme, $themes))
1916
				$id_theme = $modSettings['theme_guests'];
1917
			else
1918
				$id_theme = (int) $id_theme;
1919
		}
1920
	}
1921
1922
	// Allow mod authors the option to override the theme id for custom page themes
1923
	call_integration_hook('integrate_pre_load_theme', array(&$id_theme));
1924
1925
	// We already load the basic stuff?
1926
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme)
1927
	{
1928
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1929
1930
		if (!empty($cache_enable) && $cache_enable >= 2 && ($temp = cache_get_data('theme_settings-' . $id_theme . ':' . $member, 60)) != null && time() - 60 > $modSettings['settings_updated'])
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp = cache_get_data('...me . ':' . $member, 60) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1931
		{
1932
			$themeData = $temp;
1933
			$flag = true;
1934
		}
1935
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp = cache_get_data('...ings-' . $id_theme, 90) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1936
			$themeData = $temp + array($member => array());
1937
		else
1938
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1939
1940
		if (empty($flag))
1941
		{
1942
			// Load variables from the current or default theme, global or this user's.
1943
			$result = $smcFunc['db_query']('', '
1944
				SELECT variable, value, id_member, id_theme
1945
				FROM {db_prefix}themes
1946
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1947
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)') . '
1948
				ORDER BY id_theme asc',
1949
				array(
1950
					'id_theme' => $id_theme,
1951
					'id_member' => $member,
1952
				)
1953
			);
1954
			// Pick between $settings and $options depending on whose data it is.
1955
			foreach ($smcFunc['db_fetch_all']($result) as $row)
1956
			{
1957
				// There are just things we shouldn't be able to change as members.
1958
				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')))
1959
					continue;
1960
1961
				// If this is the theme_dir of the default theme, store it.
1962
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1963
					$themeData[0]['default_' . $row['variable']] = $row['value'];
1964
1965
				// If this isn't set yet, is a theme option, or is not the default theme..
1966
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1967
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1968
			}
1969
			$smcFunc['db_free_result']($result);
1970
1971
			if (!empty($themeData[-1]))
1972
				foreach ($themeData[-1] as $k => $v)
1973
				{
1974
					if (!isset($themeData[$member][$k]))
1975
						$themeData[$member][$k] = $v;
1976
				}
1977
1978
			if (!empty($cache_enable) && $cache_enable >= 2)
1979
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1980
			// Only if we didn't already load that part of the cache...
1981
			elseif (!isset($temp))
1982
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1983
		}
1984
1985
		$settings = $themeData[0];
1986
		$options = $themeData[$member];
1987
1988
		$settings['theme_id'] = $id_theme;
1989
1990
		$settings['actual_theme_url'] = $settings['theme_url'];
1991
		$settings['actual_images_url'] = $settings['images_url'];
1992
		$settings['actual_theme_dir'] = $settings['theme_dir'];
1993
1994
		$settings['template_dirs'] = array();
1995
		// This theme first.
1996
		$settings['template_dirs'][] = $settings['theme_dir'];
1997
1998
		// Based on theme (if there is one).
1999
		if (!empty($settings['base_theme_dir']))
2000
			$settings['template_dirs'][] = $settings['base_theme_dir'];
2001
2002
		// Lastly the default theme.
2003
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
2004
			$settings['template_dirs'][] = $settings['default_theme_dir'];
2005
	}
2006
2007
	if (!$initialize)
2008
		return;
2009
2010
	// Check to see if we're forcing SSL
2011
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
2012
		!httpsOn() && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
2013
	{
2014
		if (isset($_GET['sslRedirect']))
2015
		{
2016
			loadLanguage('Errors');
2017
			fatal_lang_error('login_ssl_required', false);
2018
		}
2019
2020
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
2021
	}
2022
2023
	// Check to see if they're accessing it from the wrong place.
2024
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
2025
	{
2026
		$detected_url = httpsOn() ? 'https://' : 'http://';
2027
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
2028
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
2029
		if ($temp != '/')
2030
			$detected_url .= $temp;
2031
	}
2032
	if (isset($detected_url) && $detected_url != $boardurl)
2033
	{
2034
		// Try #1 - check if it's in a list of alias addresses.
2035
		if (!empty($modSettings['forum_alias_urls']))
2036
		{
2037
			$aliases = explode(',', $modSettings['forum_alias_urls']);
2038
2039
			foreach ($aliases as $alias)
2040
			{
2041
				// Rip off all the boring parts, spaces, etc.
2042
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
2043
					$do_fix = true;
2044
			}
2045
		}
2046
2047
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
2048
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
2049
		{
2050
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
2051
			if (empty($_GET))
2052
				redirectexit('wwwRedirect');
2053
			else
2054
			{
2055
				$k = key($_GET);
2056
				$v = current($_GET);
2057
2058
				if ($k != 'wwwRedirect')
2059
					redirectexit('wwwRedirect;' . $k . '=' . $v);
2060
			}
2061
		}
2062
2063
		// #3 is just a check for SSL...
2064
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
2065
			$do_fix = true;
2066
2067
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
2068
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $detected_url does not seem to be defined for all execution paths leading up to this point.
Loading history...
2069
		{
2070
			// Caching is good ;).
2071
			$oldurl = $boardurl;
2072
2073
			// Fix $boardurl and $scripturl.
2074
			$boardurl = $detected_url;
2075
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
2076
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
2077
2078
			// Fix the theme urls...
2079
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
2080
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
2081
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
2082
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
2083
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
2084
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
2085
2086
			// And just a few mod settings :).
2087
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
2088
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
2089
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
2090
2091
			// Clean up after loadBoard().
2092
			if (isset($board_info['moderators']))
2093
			{
2094
				foreach ($board_info['moderators'] as $k => $dummy)
2095
				{
2096
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
2097
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
2098
				}
2099
			}
2100
			foreach ($context['linktree'] as $k => $dummy)
2101
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
2102
		}
2103
	}
2104
	// Set up the contextual user array.
2105
	if (!empty($user_info))
2106
	{
2107
		$context['user'] = array(
2108
			'id' => $user_info['id'],
2109
			'is_logged' => !$user_info['is_guest'],
2110
			'is_guest' => &$user_info['is_guest'],
2111
			'is_admin' => &$user_info['is_admin'],
2112
			'is_mod' => &$user_info['is_mod'],
2113
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
2114
			'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'])))),
2115
			'name' => $user_info['username'],
2116
			'language' => $user_info['language'],
2117
			'email' => $user_info['email'],
2118
			'ignoreusers' => $user_info['ignoreusers'],
2119
		);
2120
		if (!$context['user']['is_guest'])
2121
			$context['user']['name'] = $user_info['name'];
2122
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
2123
			$context['user']['name'] = $txt['guest_title'];
2124
2125
		// Determine the current smiley set.
2126
		$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
2127
		$user_info['smiley_set'] = (!in_array($user_info['smiley_set'], $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'];
2128
		$context['user']['smiley_set'] = $user_info['smiley_set'];
2129
	}
2130
	else
2131
	{
2132
		// What to do when there is no $user_info (e.g., an error very early in the login process)
2133
		$context['user'] = array(
2134
			'id' => -1,
2135
			'is_logged' => false,
2136
			'is_guest' => true,
2137
			'is_mod' => false,
2138
			'can_mod' => false,
2139
			'name' => $txt['guest_title'],
2140
			'language' => $language,
2141
			'email' => '',
2142
			'ignoreusers' => array(),
2143
		);
2144
		// Note we should stuff $user_info with some guest values also...
2145
		$user_info = array(
2146
			'id' => 0,
2147
			'is_guest' => true,
2148
			'is_admin' => false,
2149
			'is_mod' => false,
2150
			'username' => $txt['guest_title'],
2151
			'language' => $language,
2152
			'email' => '',
2153
			'smiley_set' => '',
2154
			'permissions' => array(),
2155
			'groups' => array(),
2156
			'ignoreusers' => array(),
2157
			'possibly_robot' => true,
2158
			'time_offset' => 0,
2159
			'time_format' => $modSettings['time_format'],
2160
		);
2161
	}
2162
2163
	// Some basic information...
2164
	if (!isset($context['html_headers']))
2165
		$context['html_headers'] = '';
2166
	if (!isset($context['javascript_files']))
2167
		$context['javascript_files'] = array();
2168
	if (!isset($context['css_files']))
2169
		$context['css_files'] = array();
2170
	if (!isset($context['css_header']))
2171
		$context['css_header'] = array();
2172
	if (!isset($context['javascript_inline']))
2173
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2174
	if (!isset($context['javascript_vars']))
2175
		$context['javascript_vars'] = array();
2176
2177
	$context['login_url'] = $scripturl . '?action=login2';
2178
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2179
	$context['session_var'] = $_SESSION['session_var'];
2180
	$context['session_id'] = $_SESSION['session_value'];
2181
	$context['forum_name'] = $mbname;
2182
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2183
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2184
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2185
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2186
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2187
	if (isset($modSettings['load_average']))
2188
		$context['load_average'] = $modSettings['load_average'];
2189
2190
	// Detect the browser. This is separated out because it's also used in attachment downloads
2191
	detectBrowser();
2192
2193
	// Set the top level linktree up.
2194
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2195
	if (empty($context['linktree']))
2196
		$context['linktree'] = array();
2197
	array_unshift($context['linktree'], array(
2198
		'url' => $scripturl,
2199
		'name' => $context['forum_name_html_safe']
2200
	));
2201
2202
	// This allows sticking some HTML on the page output - useful for controls.
2203
	$context['insert_after_template'] = '';
2204
2205
	if (!isset($txt))
2206
		$txt = array();
2207
2208
	$simpleActions = array(
2209
		'findmember',
2210
		'helpadmin',
2211
		'printpage',
2212
		'spellcheck',
2213
	);
2214
2215
	// Parent action => array of areas
2216
	$simpleAreas = array(
2217
		'profile' => array('popup', 'alerts_popup',),
2218
	);
2219
2220
	// Parent action => array of subactions
2221
	$simpleSubActions = array(
2222
		'pm' => array('popup',),
2223
		'signup' => array('usernamecheck'),
2224
	);
2225
2226
	// Extra params like ;preview ;js, etc.
2227
	$extraParams = array(
2228
		'preview',
2229
		'splitjs',
2230
	);
2231
2232
	// Actions that specifically uses XML output.
2233
	$xmlActions = array(
2234
		'quotefast',
2235
		'jsmodify',
2236
		'xmlhttp',
2237
		'post2',
2238
		'suggest',
2239
		'stats',
2240
		'notifytopic',
2241
		'notifyboard',
2242
	);
2243
2244
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2245
2246
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2247
		(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2248
		(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2249
2250
	// See if theres any extra param to check.
2251
	$requiresXML = false;
2252
	foreach ($extraParams as $key => $extra)
2253
		if (isset($_REQUEST[$extra]))
2254
			$requiresXML = true;
2255
2256
	// Output is fully XML, so no need for the index template.
2257
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2258
	{
2259
		loadLanguage('index+Modifications');
2260
		loadTemplate('Xml');
2261
		$context['template_layers'] = array();
2262
	}
2263
2264
	// These actions don't require the index template at all.
2265
	elseif (!empty($context['simple_action']))
2266
	{
2267
		loadLanguage('index+Modifications');
2268
		$context['template_layers'] = array();
2269
	}
2270
2271
	else
2272
	{
2273
		// Custom templates to load, or just default?
2274
		if (isset($settings['theme_templates']))
2275
			$templates = explode(',', $settings['theme_templates']);
2276
		else
2277
			$templates = array('index');
2278
2279
		// Load each template...
2280
		foreach ($templates as $template)
2281
			loadTemplate($template);
2282
2283
		// ...and attempt to load their associated language files.
2284
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2285
		loadLanguage($required_files, '', false);
2286
2287
		// Custom template layers?
2288
		if (isset($settings['theme_layers']))
2289
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2290
		else
2291
			$context['template_layers'] = array('html', 'body');
2292
	}
2293
2294
	// Initialize the theme.
2295
	loadSubTemplate('init', 'ignore');
0 ignored issues
show
Bug introduced by
'ignore' of type string is incompatible with the type boolean expected by parameter $fatal of loadSubTemplate(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2295
	loadSubTemplate('init', /** @scrutinizer ignore-type */ 'ignore');
Loading history...
2296
2297
	// Allow overriding the board wide time/number formats.
2298
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2299
		$user_info['time_format'] = $txt['time_format'];
2300
2301
	// Set the character set from the template.
2302
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2303
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2304
2305
	// Guests may still need a name.
2306
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2307
		$context['user']['name'] = $txt['guest_title'];
2308
2309
	// Any theme-related strings that need to be loaded?
2310
	if (!empty($settings['require_theme_strings']))
2311
		loadLanguage('ThemeStrings', '', false);
2312
2313
	// Make a special URL for the language.
2314
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2315
2316
	// And of course, let's load the default CSS file.
2317
	loadCSSFile('index.css', array('minimize' => true, 'order_pos' => 1), 'smf_index');
2318
2319
	// Here is my luvly Responsive CSS
2320
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true, 'order_pos' => 9000), 'smf_responsive');
2321
2322
	if ($context['right_to_left'])
2323
		loadCSSFile('rtl.css', array('order_pos' => 4000), 'smf_rtl');
2324
2325
	// We allow theme variants, because we're cool.
2326
	$context['theme_variant'] = '';
2327
	$context['theme_variant_url'] = '';
2328
	if (!empty($settings['theme_variants']))
2329
	{
2330
		// Overriding - for previews and that ilk.
2331
		if (!empty($_REQUEST['variant']))
2332
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2333
		// User selection?
2334
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2335
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2336
		// If not a user variant, select the default.
2337
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2338
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2339
2340
		// Do this to keep things easier in the templates.
2341
		$context['theme_variant'] = '_' . $context['theme_variant'];
2342
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2343
2344
		if (!empty($context['theme_variant']))
2345
		{
2346
			loadCSSFile('index' . $context['theme_variant'] . '.css', array('order_pos' => 300), 'smf_index' . $context['theme_variant']);
2347
			if ($context['right_to_left'])
2348
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array('order_pos' => 4200), 'smf_rtl' . $context['theme_variant']);
2349
		}
2350
	}
2351
2352
	// Let's be compatible with old themes!
2353
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2354
		$context['template_layers'] = array('main');
2355
2356
	$context['tabindex'] = 1;
2357
2358
	// Compatibility.
2359
	if (!isset($settings['theme_version']))
2360
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2361
2362
	// Default JS variables for use in every theme
2363
	$context['javascript_vars'] = array(
2364
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2365
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2366
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2367
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2368
		'smf_smiley_sets' => '"' . $modSettings['smiley_sets_known'] . '"',
2369
		'smf_smiley_sets_default' => '"' . $modSettings['smiley_sets_default'] . '"',
2370
		'smf_scripturl' => '"' . $scripturl . '"',
2371
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2372
		'smf_charset' => '"' . $context['character_set'] . '"',
2373
		'smf_session_id' => '"' . $context['session_id'] . '"',
2374
		'smf_session_var' => '"' . $context['session_var'] . '"',
2375
		'smf_member_id' => $context['user']['id'],
2376
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2377
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2378
		'banned_text' => JavaScriptEscape(sprintf($txt['your_ban'], $context['user']['name'])),
2379
	);
2380
2381
	// Add the JQuery library to the list of files to load.
2382
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2383
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2384
2385
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2386
		loadJavaScriptFile('jquery-' . JQUERY_VERSION . '.min.js', array('seed' => false), 'smf_jquery');
2387
2388
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2389
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2390
2391
	// Auto loading? template_javascript() will take care of the local half of this.
2392
	else
2393
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2394
2395
	// Queue our JQuery plugins!
2396
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2397
	if (!$user_info['is_guest'])
2398
	{
2399
		loadJavaScriptFile('jquery.custom-scrollbar.js', array('minimize' => true), 'smf_jquery_scrollbar');
2400
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2401
	}
2402
2403
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2404
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2405
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2406
2407
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2408
	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())
2409
	{
2410
		if (isBrowser('possibly_robot'))
2411
		{
2412
			// @todo Maybe move this somewhere better?!
2413
			require_once($sourcedir . '/ScheduledTasks.php');
2414
2415
			// What to do, what to do?!
2416
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2417
				AutoTask();
2418
			else
2419
				ReduceMailQueue();
2420
		}
2421
		else
2422
		{
2423
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2424
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
2425
2426
			addInlineJavaScript('
2427
		function smfAutoTask()
2428
		{
2429
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2430
		}
2431
		window.setTimeout("smfAutoTask();", 1);');
2432
		}
2433
	}
2434
2435
	// And we should probably trigger the cron too.
2436
	if (empty($modSettings['cron_is_real_cron']))
2437
	{
2438
		$ts = time();
2439
		$ts -= $ts % 15;
2440
		addInlineJavaScript('
2441
	function triggerCron()
2442
	{
2443
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2444
	}
2445
	window.setTimeout(triggerCron, 1);', true);
2446
	}
2447
2448
	// Filter out the restricted boards from the linktree
2449
	if (!$user_info['is_admin'] && !empty($board))
2450
	{
2451
		foreach ($context['linktree'] as $k => $element)
2452
		{
2453
			if (!empty($element['groups']) &&
2454
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2455
					(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2456
			{
2457
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2458
				$context['linktree'][$k]['extra_before'] = '<i>';
2459
				$context['linktree'][$k]['extra_after'] = '</i>';
2460
				unset($context['linktree'][$k]['url']);
2461
			}
2462
		}
2463
	}
2464
2465
	// Any files to include at this point?
2466
	if (!empty($modSettings['integrate_theme_include']))
2467
	{
2468
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2469
		foreach ($theme_includes as $include)
2470
		{
2471
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2472
			if (file_exists($include))
2473
				require_once($include);
2474
		}
2475
	}
2476
2477
	// Call load theme integration functions.
2478
	call_integration_hook('integrate_load_theme');
2479
2480
	// We are ready to go.
2481
	$context['theme_loaded'] = true;
2482
}
2483
2484
/**
2485
 * Load a template - if the theme doesn't include it, use the default.
2486
 * What this function does:
2487
 *  - loads a template file with the name template_name from the current, default, or base theme.
2488
 *  - detects a wrong default theme directory and tries to work around it.
2489
 *
2490
 * @uses the template_include() function to include the file.
2491
 * @param string $template_name The name of the template to load
2492
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2493
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2494
 * @return boolean Whether or not the template was loaded
2495
 */
2496
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2497
{
2498
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
2499
2500
	// Do any style sheets first, cause we're easy with those.
2501
	if (!empty($style_sheets))
2502
	{
2503
		if (!is_array($style_sheets))
2504
			$style_sheets = array($style_sheets);
2505
2506
		foreach ($style_sheets as $sheet)
2507
			loadCSSFile($sheet . '.css', array(), $sheet);
2508
	}
2509
2510
	// No template to load?
2511
	if ($template_name === false)
0 ignored issues
show
introduced by
The condition $template_name === false is always false.
Loading history...
2512
		return true;
2513
2514
	$loaded = false;
2515
	foreach ($settings['template_dirs'] as $template_dir)
2516
	{
2517
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2518
		{
2519
			$loaded = true;
2520
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2521
			break;
2522
		}
2523
	}
2524
2525
	if ($loaded)
0 ignored issues
show
introduced by
The condition $loaded is always false.
Loading history...
2526
	{
2527
		if ($db_show_debug === true)
2528
			$context['debug']['templates'][] = $template_name . ' (' . basename($template_dir) . ')';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $template_dir seems to be defined by a foreach iteration on line 2515. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2529
2530
		// If they have specified an initialization function for this template, go ahead and call it now.
2531
		if (function_exists('template_' . $template_name . '_init'))
2532
			call_user_func('template_' . $template_name . '_init');
2533
	}
2534
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2535
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2536
	{
2537
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2538
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2539
2540
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2541
		{
2542
			loadLanguage('Errors');
2543
			echo '
2544
<div class="alert errorbox">
2545
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2546
</div>';
2547
		}
2548
2549
		loadTemplate($template_name);
2550
	}
2551
	// Cause an error otherwise.
2552
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2553
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2554
	elseif ($fatal)
2555
		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'));
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
2556
	else
2557
		return false;
2558
}
2559
2560
/**
2561
 * Load a sub-template.
2562
 * What it does:
2563
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2564
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2565
 *	for debugging purposes.
2566
 *
2567
 * @todo get rid of reading $_REQUEST directly
2568
 *
2569
 * @param string $sub_template_name The name of the sub-template to load
2570
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2571
 */
2572
function loadSubTemplate($sub_template_name, $fatal = false)
2573
{
2574
	global $context, $txt, $db_show_debug;
2575
2576
	if ($db_show_debug === true)
2577
		$context['debug']['sub_templates'][] = $sub_template_name;
2578
2579
	// Figure out what the template function is named.
2580
	$theme_function = 'template_' . $sub_template_name;
2581
	if (function_exists($theme_function))
2582
		$theme_function();
2583
	elseif ($fatal === false)
2584
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2585
	elseif ($fatal !== 'ignore')
0 ignored issues
show
introduced by
The condition $fatal !== 'ignore' is always true.
Loading history...
2586
		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'));
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
2587
2588
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2589
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2590
	{
2591
		echo '
2592
<div class="warningbox">---- ', $sub_template_name, ' ends ----</div>';
2593
	}
2594
}
2595
2596
/**
2597
 * Add a CSS file for output later
2598
 *
2599
 * @param string $fileName The name of the file to load
2600
 * @param array $params An array of parameters
2601
 * Keys are the following:
2602
 * 	- ['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
2603
 * 	- ['default_theme'] (true/false): force use of default theme url
2604
 * 	- ['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
2605
 *  - ['validate'] (true/false): if true script will validate the local file exists
2606
 *  - ['rtl'] (string): additional file to load in RTL mode
2607
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2608
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2609
 *  - ['order_pos'] int define the load order, when not define it's loaded in the middle, before index.css = -500, after index.css = 500, middle = 3000, end (i.e. after responsive.css) = 10000
2610
 *  - ['attributes'] array extra attributes to add to the element
2611
 * @param string $id An ID to stick on the end of the filename for caching purposes
2612
 */
2613
function loadCSSFile($fileName, $params = array(), $id = '')
2614
{
2615
	global $settings, $context, $modSettings;
2616
2617
	if (empty($context['css_files_order']))
2618
		$context['css_files_order'] = array();
2619
2620
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ? (array_key_exists('browser_cache', $context) ? $context['browser_cache'] : '') : (is_string($params['seed']) ? '?' . ltrim($params['seed'], '?') : '');
2621
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2622
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2623
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : true;
2624
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2625
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2626
	$params['order_pos'] = isset($params['order_pos']) ? (int) $params['order_pos'] : 3000;
2627
2628
	// If this is an external file, automatically set this to false.
2629
	if (!empty($params['external']))
2630
		$params['minimize'] = false;
2631
2632
	// Account for shorthand like admin.css?alp21 filenames
2633
	$id = empty($id) ? strtr(str_replace('.css', '', basename($fileName)), '?', '_') : $id;
2634
	$fileName = str_replace(pathinfo($fileName, PATHINFO_EXTENSION), strtok(pathinfo($fileName, PATHINFO_EXTENSION), '?'), $fileName);
2635
2636
	// Is this a local file?
2637
	if (empty($params['external']))
2638
	{
2639
		// Are we validating the the file exists?
2640
		if (!empty($params['validate']) && ($mtime = @filemtime($settings[$themeRef . '_dir'] . '/css/' . $fileName)) === false)
2641
		{
2642
			// Maybe the default theme has it?
2643
			if ($themeRef === 'theme' && !$params['force_current'] && ($mtime = @filemtime($settings['default_theme_dir'] . '/css/' . $fileName) !== false))
2644
			{
2645
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName;
2646
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName;
2647
			}
2648
2649
			else
2650
			{
2651
				$fileUrl = false;
2652
				$filePath = false;
2653
			}
2654
		}
2655
2656
		else
2657
		{
2658
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName;
2659
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName;
2660
			$mtime = @filemtime($filePath);
2661
		}
2662
	}
2663
2664
	// An external file doesn't have a filepath. Mock one for simplicity.
2665
	else
2666
	{
2667
		$fileUrl = $fileName;
2668
		$filePath = $fileName;
2669
	}
2670
2671
	$mtime = empty($mtime) ? 0 : $mtime;
2672
2673
	// Add it to the array for use in the template
2674
	if (!empty($fileName))
2675
	{
2676
		// find a free number/position
2677
		while (isset($context['css_files_order'][$params['order_pos']]))
2678
			$params['order_pos']++;
2679
		$context['css_files_order'][$params['order_pos']] = $id;
2680
2681
		$context['css_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params, 'mtime' => $mtime);
2682
	}
2683
2684
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2685
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2686
2687
	if ($mtime > $modSettings['browser_cache'])
2688
		updateSettings(array('browser_cache' => $mtime));
2689
}
2690
2691
/**
2692
 * Add a block of inline css code to be executed later
2693
 *
2694
 * - only use this if you have to, generally external css files are better, but for very small changes
2695
 *   or for scripts that require help from PHP/whatever, this can be useful.
2696
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2697
 *
2698
 * @param string $css Some css code
2699
 * @return void|bool Adds the CSS to the $context['css_header'] array or returns if no CSS is specified
2700
 */
2701
function addInlineCss($css)
2702
{
2703
	global $context;
2704
2705
	// Gotta add something...
2706
	if (empty($css))
2707
		return false;
2708
2709
	$context['css_header'][] = $css;
2710
}
2711
2712
/**
2713
 * Add a Javascript file for output later
2714
 *
2715
 * @param string $fileName The name of the file to load
2716
 * @param array $params An array of parameter info
2717
 * Keys are the following:
2718
 * 	- ['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
2719
 * 	- ['default_theme'] (true/false): force use of default theme url
2720
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2721
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2722
 *	default theme if not found in the current theme
2723
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2724
 *  - ['validate'] (true/false): if true script will validate the local file exists
2725
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2726
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2727
 *  - ['attributes'] array extra attributes to add to the element
2728
 *
2729
 * @param string $id An ID to stick on the end of the filename
2730
 */
2731
function loadJavaScriptFile($fileName, $params = array(), $id = '')
2732
{
2733
	global $settings, $context, $modSettings;
2734
2735
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ? (array_key_exists('browser_cache', $context) ? $context['browser_cache'] : '') : (is_string($params['seed']) ? '?' . ltrim($params['seed'], '?') : '');
2736
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2737
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2738
	$params['async'] = isset($params['async']) ? $params['async'] : false;
2739
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2740
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2741
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2742
2743
	// If this is an external file, automatically set this to false.
2744
	if (!empty($params['external']))
2745
		$params['minimize'] = false;
2746
2747
	// Account for shorthand like admin.js?alp21 filenames
2748
	$id = empty($id) ? strtr(str_replace('.js', '', basename($fileName)), '?', '_') : $id;
2749
	$fileName = str_replace(pathinfo($fileName, PATHINFO_EXTENSION), strtok(pathinfo($fileName, PATHINFO_EXTENSION), '?'), $fileName);
2750
2751
	// Is this a local file?
2752
	if (empty($params['external']))
2753
	{
2754
		// Are we validating it exists on disk?
2755
		if (!empty($params['validate']) && ($mtime = @filemtime($settings[$themeRef . '_dir'] . '/scripts/' . $fileName)) === false)
2756
		{
2757
			// Can't find it in this theme, how about the default?
2758
			if ($themeRef === 'theme' && !$params['force_current'] && ($mtime = @filemtime($settings['default_theme_dir'] . '/scripts/' . $fileName)) !== false)
2759
			{
2760
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName;
2761
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName;
2762
			}
2763
2764
			else
2765
			{
2766
				$fileUrl = false;
2767
				$filePath = false;
2768
			}
2769
		}
2770
2771
		else
2772
		{
2773
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName;
2774
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName;
2775
			$mtime = @filemtime($filePath);
2776
		}
2777
	}
2778
2779
	// An external file doesn't have a filepath. Mock one for simplicity.
2780
	else
2781
	{
2782
		$fileUrl = $fileName;
2783
		$filePath = $fileName;
2784
	}
2785
2786
	$mtime = empty($mtime) ? 0 : $mtime;
2787
2788
	// Add it to the array for use in the template
2789
	if (!empty($fileName))
2790
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params, 'mtime' => $mtime);
2791
2792
	if ($mtime > $modSettings['browser_cache'])
2793
		updateSettings(array('browser_cache' => $mtime));
2794
}
2795
2796
/**
2797
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2798
 * Cleaner and easier (for modders) than to use the function below.
2799
 *
2800
 * @param string $key The key for this variable
2801
 * @param string $value The value
2802
 * @param bool $escape Whether or not to escape the value
2803
 */
2804
function addJavaScriptVar($key, $value, $escape = false)
2805
{
2806
	global $context;
2807
2808
	if (!empty($key) && (!empty($value) || $value === '0'))
2809
		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
2810
}
2811
2812
/**
2813
 * Add a block of inline Javascript code to be executed later
2814
 *
2815
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2816
 *   or for scripts that require help from PHP/whatever, this can be useful.
2817
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2818
 *
2819
 * @param string $javascript Some JS code
2820
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2821
 * @return void|bool Adds the code to one of the $context['javascript_inline'] arrays or returns if no JS was specified
2822
 */
2823
function addInlineJavaScript($javascript, $defer = false)
2824
{
2825
	global $context;
2826
2827
	if (empty($javascript))
2828
		return false;
2829
2830
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2831
}
2832
2833
/**
2834
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2835
 *
2836
 * @param string $template_name The name of a template file
2837
 * @param string $lang A specific language to load this file from
2838
 * @param bool $fatal Whether to die with an error if it can't be loaded
2839
 * @param bool $force_reload Whether to load the file again if it's already loaded
2840
 * @return string The language actually loaded.
2841
 */
2842
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2843
{
2844
	global $user_info, $language, $settings, $context, $modSettings;
2845
	global $db_show_debug, $sourcedir, $txt, $birthdayEmails, $txtBirthdayEmails;
2846
	static $already_loaded = array();
2847
2848
	// Default to the user's language.
2849
	if ($lang == '')
2850
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2851
2852
	// Do we want the English version of language file as fallback?
2853
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2854
		loadLanguage($template_name, 'english', false);
2855
2856
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2857
		return $lang;
2858
2859
	// Make sure we have $settings - if not we're in trouble and need to find it!
2860
	if (empty($settings['default_theme_dir']))
2861
	{
2862
		require_once($sourcedir . '/ScheduledTasks.php');
2863
		loadEssentialThemeData();
2864
	}
2865
2866
	// What theme are we in?
2867
	$theme_name = basename($settings['theme_url']);
2868
	if (empty($theme_name))
2869
		$theme_name = 'unknown';
2870
2871
	// For each file open it up and write it out!
2872
	foreach (explode('+', $template_name) as $template)
2873
	{
2874
		// Obviously, the current theme is most important to check.
2875
		$attempts = array(
2876
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2877
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2878
		);
2879
2880
		// Do we have a base theme to worry about?
2881
		if (isset($settings['base_theme_dir']))
2882
		{
2883
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2884
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2885
		}
2886
2887
		// Fall back on the default theme if necessary.
2888
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2889
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2890
2891
		// Fall back on the English language if none of the preferred languages can be found.
2892
		if (!in_array('english', array($lang, $language)))
2893
		{
2894
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2895
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2896
		}
2897
2898
		// Try to find the language file.
2899
		$found = false;
2900
		foreach ($attempts as $k => $file)
2901
		{
2902
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2903
			{
2904
				// Include it!
2905
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2906
2907
				// Note that we found it.
2908
				$found = true;
2909
2910
				// setlocale is required for basename() & pathinfo() to work properly on the selected language
2911
				if (!empty($txt['lang_locale']) && !empty($modSettings['global_character_set']))
2912
					setlocale(LC_CTYPE, $txt['lang_locale'] . '.' . $modSettings['global_character_set']);
2913
2914
				break;
2915
			}
2916
		}
2917
2918
		// That couldn't be found!  Log the error, but *try* to continue normally.
2919
		if (!$found && $fatal)
2920
		{
2921
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2922
			break;
2923
		}
2924
2925
		// For the sake of backward compatibility
2926
		if (!empty($txt['emails']))
2927
		{
2928
			foreach ($txt['emails'] as $key => $value)
2929
			{
2930
				$txt[$key . '_subject'] = $value['subject'];
2931
				$txt[$key . '_body'] = $value['body'];
2932
			}
2933
			$txt['emails'] = array();
2934
		}
2935
		// For sake of backward compatibility: $birthdayEmails is supposed to be
2936
		// empty in a normal install. If it isn't it means the forum is using
2937
		// something "old" (it may be the translation, it may be a mod) and this
2938
		// code (like the piece above) takes care of converting it to the new format
2939
		if (!empty($birthdayEmails))
2940
		{
2941
			foreach ($birthdayEmails as $key => $value)
2942
			{
2943
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
2944
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
2945
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
2946
			}
2947
			$birthdayEmails = array();
2948
		}
2949
	}
2950
2951
	// Keep track of what we're up to soldier.
2952
	if ($db_show_debug === true)
2953
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
2954
2955
	// Remember what we have loaded, and in which language.
2956
	$already_loaded[$template_name] = $lang;
2957
2958
	// Return the language actually loaded.
2959
	return $lang;
2960
}
2961
2962
/**
2963
 * Get all parent boards (requires first parent as parameter)
2964
 * It finds all the parents of id_parent, and that board itself.
2965
 * Additionally, it detects the moderators of said boards.
2966
 *
2967
 * @param int $id_parent The ID of the parent board
2968
 * @return array An array of information about the boards found.
2969
 */
2970
function getBoardParents($id_parent)
2971
{
2972
	global $scripturl, $smcFunc;
2973
2974
	// First check if we have this cached already.
2975
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
2976
	{
2977
		$boards = array();
2978
		$original_parent = $id_parent;
2979
2980
		// Loop while the parent is non-zero.
2981
		while ($id_parent != 0)
2982
		{
2983
			$result = $smcFunc['db_query']('', '
2984
				SELECT
2985
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
2986
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
2987
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
2988
				FROM {db_prefix}boards AS b
2989
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
2990
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
2991
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
2992
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
2993
				WHERE b.id_board = {int:board_parent}',
2994
				array(
2995
					'board_parent' => $id_parent,
2996
				)
2997
			);
2998
			// In the EXTREMELY unlikely event this happens, give an error message.
2999
			if ($smcFunc['db_num_rows']($result) == 0)
3000
				fatal_lang_error('parent_not_found', 'critical');
3001
			while ($row = $smcFunc['db_fetch_assoc']($result))
3002
			{
3003
				if (!isset($boards[$row['id_board']]))
3004
				{
3005
					$id_parent = $row['id_parent'];
3006
					$boards[$row['id_board']] = array(
3007
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
3008
						'name' => $row['name'],
3009
						'level' => $row['child_level'],
3010
						'groups' => explode(',', $row['member_groups']),
3011
						'deny_groups' => explode(',', $row['deny_member_groups']),
3012
						'moderators' => array(),
3013
						'moderator_groups' => array()
3014
					);
3015
				}
3016
				// If a moderator exists for this board, add that moderator for all children too.
3017
				if (!empty($row['id_moderator']))
3018
					foreach ($boards as $id => $dummy)
3019
					{
3020
						$boards[$id]['moderators'][$row['id_moderator']] = array(
3021
							'id' => $row['id_moderator'],
3022
							'name' => $row['real_name'],
3023
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
3024
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
3025
						);
3026
					}
3027
3028
				// If a moderator group exists for this board, add that moderator group for all children too
3029
				if (!empty($row['id_moderator_group']))
3030
					foreach ($boards as $id => $dummy)
3031
					{
3032
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
3033
							'id' => $row['id_moderator_group'],
3034
							'name' => $row['group_name'],
3035
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
3036
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
3037
						);
3038
					}
3039
			}
3040
			$smcFunc['db_free_result']($result);
3041
		}
3042
3043
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
3044
	}
3045
3046
	return $boards;
3047
}
3048
3049
/**
3050
 * Attempt to reload our known languages.
3051
 * It will try to choose only utf8 or non-utf8 languages.
3052
 *
3053
 * @param bool $use_cache Whether or not to use the cache
3054
 * @return array An array of information about available languages
3055
 */
3056
function getLanguages($use_cache = true)
3057
{
3058
	global $context, $smcFunc, $settings, $modSettings, $cache_enable;
3059
3060
	// Either we don't use the cache, or its expired.
3061
	if (!$use_cache || ($context['languages'] = cache_get_data('known_languages', !empty($cache_enable) && $cache_enable < 1 ? 86400 : 3600)) == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $context['languages'] = ...ble < 1 ? 86400 : 3600) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
3062
	{
3063
		// If we don't have our ucwords function defined yet, let's load the settings data.
3064
		if (empty($smcFunc['ucwords']))
3065
			reloadSettings();
3066
3067
		// If we don't have our theme information yet, let's get it.
3068
		if (empty($settings['default_theme_dir']))
3069
			loadTheme(0, false);
3070
3071
		// Default language directories to try.
3072
		$language_directories = array(
3073
			$settings['default_theme_dir'] . '/languages',
3074
		);
3075
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
3076
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
3077
3078
		// We possibly have a base theme directory.
3079
		if (!empty($settings['base_theme_dir']))
3080
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
3081
3082
		// Remove any duplicates.
3083
		$language_directories = array_unique($language_directories);
3084
3085
		foreach ($language_directories as $language_dir)
3086
		{
3087
			// Can't look in here... doesn't exist!
3088
			if (!file_exists($language_dir))
3089
				continue;
3090
3091
			$dir = dir($language_dir);
3092
			while ($entry = $dir->read())
3093
			{
3094
				// Look for the index language file... For good measure skip any "index.language-utf8.php" files
3095
				if (!preg_match('~^index\.((?:.(?!-utf8))+)\.php$~', $entry, $matches))
3096
					continue;
3097
3098
				$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
3099
3100
				if (($spos = strpos($langName, ' ')) !== false)
3101
					$langName = substr($langName, 0, ++$spos) . '(' . substr($langName, $spos) . ')';
3102
3103
				// Get the line we need.
3104
				$fp = @fopen($language_dir . '/' . $entry, 'r');
3105
3106
				// Yay!
3107
				if ($fp)
3108
				{
3109
					while (($line = fgets($fp)) !== false)
3110
					{
3111
						if (strpos($line, '$txt[\'native_name\']') === false)
3112
							continue;
3113
3114
						preg_match('~\$txt\[\'native_name\'\]\s*=\s*\'([^\']+)\';~', $line, $matchNative);
3115
3116
						// Set the language's name.
3117
						if (!empty($matchNative) && !empty($matchNative[1]))
3118
						{
3119
							// Don't mislabel the language if the translator missed this one.
3120
							if ($langName !== 'English' && $matchNative[1] === 'English')
3121
								break;
3122
3123
							$langName = un_htmlspecialchars($matchNative[1]);
3124
							break;
3125
						}
3126
					}
3127
3128
					fclose($fp);
3129
				}
3130
3131
				// Build this language entry.
3132
				$context['languages'][$matches[1]] = array(
3133
					'name' => $langName,
3134
					'selected' => false,
3135
					'filename' => $matches[1],
3136
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
3137
				);
3138
			}
3139
			$dir->close();
3140
		}
3141
3142
		// Avoid confusion when we have more than one English variant installed.
3143
		// Honestly, our default English version should always have been called "English (US)"
3144
		if (substr_count(implode(' ', array_keys($context['languages'])), 'english') > 1 && $context['languages']['english']['name'] === 'English')
3145
			$context['languages']['english']['name'] = 'English (US)';
3146
3147
		// Let's cash in on this deal.
3148
		if (!empty($cache_enable))
3149
			cache_put_data('known_languages', $context['languages'], !empty($cache_enable) && $cache_enable < 1 ? 86400 : 3600);
3150
	}
3151
3152
	return $context['languages'];
3153
}
3154
3155
/**
3156
 * Replace all vulgar words with respective proper words. (substring or whole words..)
3157
 * What this function does:
3158
 *  - it censors the passed string.
3159
 *  - if the theme setting allow_no_censored is on, and the theme option
3160
 *	show_no_censored is enabled, does not censor, unless force is also set.
3161
 *  - it caches the list of censored words to reduce parsing.
3162
 *
3163
 * @param string &$text The text to censor
3164
 * @param bool $force Whether to censor the text regardless of settings
3165
 * @return string The censored text
3166
 */
3167
function censorText(&$text, $force = false)
3168
{
3169
	global $modSettings, $options, $txt;
3170
	static $censor_vulgar = null, $censor_proper;
3171
3172
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
3173
		return $text;
3174
3175
	// If they haven't yet been loaded, load them.
3176
	if ($censor_vulgar == null)
3177
	{
3178
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
3179
		$censor_proper = explode("\n", $modSettings['censor_proper']);
3180
3181
		// Quote them for use in regular expressions.
3182
		if (!empty($modSettings['censorWholeWord']))
3183
		{
3184
			$charset = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
3185
3186
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
3187
			{
3188
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
3189
3190
				// Use the faster \b if we can, or something more complex if we can't
3191
				$boundary_before = preg_match('/^\w/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?<![\p{L}\p{M}\p{N}_])' : '(?<!\w)');
3192
				$boundary_after = preg_match('/\w$/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?![\p{L}\p{M}\p{N}_])' : '(?!\w)');
3193
3194
				$censor_vulgar[$i] = '/' . $boundary_before . $censor_vulgar[$i] . $boundary_after . '/' . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ($charset === 'UTF-8' ? 'u' : '');
3195
			}
3196
		}
3197
	}
3198
3199
	// Censoring isn't so very complicated :P.
3200
	if (empty($modSettings['censorWholeWord']))
3201
	{
3202
		$func = !empty($modSettings['censorIgnoreCase']) ? 'str_ireplace' : 'str_replace';
3203
		$text = $func($censor_vulgar, $censor_proper, $text);
3204
	}
3205
	else
3206
		$text = preg_replace($censor_vulgar, $censor_proper, $text);
3207
3208
	return $text;
3209
}
3210
3211
/**
3212
 * Load the template/language file using require
3213
 * 	- loads the template or language file specified by filename.
3214
 * 	- uses eval unless disableTemplateEval is enabled.
3215
 * 	- outputs a parse error if the file did not exist or contained errors.
3216
 * 	- attempts to detect the error and line, and show detailed information.
3217
 *
3218
 * @param string $filename The name of the file to include
3219
 * @param bool $once If true only includes the file once (like include_once)
3220
 */
3221
function template_include($filename, $once = false)
3222
{
3223
	global $context, $txt, $scripturl, $modSettings;
3224
	global $boardurl, $boarddir;
3225
	global $maintenance, $mtitle, $mmessage;
3226
	static $templates = array();
3227
3228
	// We want to be able to figure out any errors...
3229
	@ini_set('track_errors', '1');
3230
3231
	// Don't include the file more than once, if $once is true.
3232
	if ($once && in_array($filename, $templates))
3233
		return;
3234
	// Add this file to the include list, whether $once is true or not.
3235
	else
3236
		$templates[] = $filename;
3237
3238
	$file_found = file_exists($filename);
3239
3240
	if ($once && $file_found)
3241
		require_once($filename);
3242
	elseif ($file_found)
3243
		require($filename);
3244
3245
	if ($file_found !== true)
3246
	{
3247
		ob_end_clean();
3248
		if (!empty($modSettings['enableCompressedOutput']))
3249
			@ob_start('ob_gzhandler');
3250
		else
3251
			ob_start();
3252
3253
		if (isset($_GET['debug']))
3254
			header('content-type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3255
3256
		// Don't cache error pages!!
3257
		header('expires: Mon, 26 Jul 1997 05:00:00 GMT');
3258
		header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3259
		header('cache-control: no-cache');
3260
3261
		if (!isset($txt['template_parse_error']))
3262
		{
3263
			$txt['template_parse_error'] = 'Template Parse Error!';
3264
			$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>.';
3265
			$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>.';
3266
			$txt['template_parse_errmsg'] = 'Unfortunately more information is not available at this time as to exactly what is wrong.';
3267
		}
3268
3269
		// First, let's get the doctype and language information out of the way.
3270
		echo '<!DOCTYPE html>
3271
<html', !empty($context['right_to_left']) ? ' dir="rtl"' : '', '>
3272
	<head>';
3273
		if (isset($context['character_set']))
3274
			echo '
3275
		<meta charset="', $context['character_set'], '">';
3276
3277
		if (!empty($maintenance) && !allowedTo('admin_forum'))
3278
			echo '
3279
		<title>', $mtitle, '</title>
3280
	</head>
3281
	<body>
3282
		<h3>', $mtitle, '</h3>
3283
		', $mmessage, '
3284
	</body>
3285
</html>';
3286
		elseif (!allowedTo('admin_forum'))
3287
			echo '
3288
		<title>', $txt['template_parse_error'], '</title>
3289
	</head>
3290
	<body>
3291
		<h3>', $txt['template_parse_error'], '</h3>
3292
		', $txt['template_parse_error_message'], '
3293
	</body>
3294
</html>';
3295
		else
3296
		{
3297
			$error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3298
			$error_array = error_get_last();
3299
			if (empty($error) && ini_get('track_errors') && !empty($error_array))
3300
				$error = $error_array['message'];
3301
			if (empty($error))
3302
				$error = $txt['template_parse_errmsg'];
3303
3304
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
3305
3306
			echo '
3307
		<title>', $txt['template_parse_error'], '</title>
3308
	</head>
3309
	<body>
3310
		<h3>', $txt['template_parse_error'], '</h3>
3311
		', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3312
3313
			if (!empty($error))
3314
				echo '
3315
		<hr>
3316
3317
		<div style="margin: 0 20px;"><pre>', strtr(strtr($error, array('<strong>' . $boarddir => '<strong>...', '<strong>' . strtr($boarddir, '\\', '/') => '<strong>...')), '\\', '/'), '</pre></div>';
3318
3319
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
3320
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
3321
			{
3322
				$data = file($filename);
3323
				$data2 = highlight_php_code(implode('', $data));
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3323
				$data2 = highlight_php_code(implode('', /** @scrutinizer ignore-type */ $data));
Loading history...
3324
				$data2 = preg_split('~\<br( /)?\>~', $data2);
3325
3326
				// Fix the PHP code stuff...
3327
				if (!isBrowser('gecko'))
3328
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
3329
				else
3330
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
3331
3332
				// Now we get to work around a bug in PHP where it doesn't escape <br>s!
3333
				$j = -1;
3334
				foreach ($data as $line)
3335
				{
3336
					$j++;
3337
3338
					if (substr_count($line, '<br>') == 0)
3339
						continue;
3340
3341
					$n = substr_count($line, '<br>');
3342
					for ($i = 0; $i < $n; $i++)
3343
					{
3344
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
3345
						unset($data2[$j + $i + 1]);
3346
					}
3347
					$j += $n;
3348
				}
3349
				$data2 = array_values($data2);
3350
				array_unshift($data2, '');
3351
3352
				echo '
3353
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
3354
3355
				// Figure out what the color coding was before...
3356
				$line = max($match[1] - 9, 1);
3357
				$last_line = '';
3358
				for ($line2 = $line - 1; $line2 > 1; $line2--)
3359
					if (strpos($data2[$line2], '<') !== false)
3360
					{
3361
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
3362
							$last_line = $color_match[1];
3363
						break;
3364
					}
3365
3366
				// Show the relevant lines...
3367
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
3368
				{
3369
					if ($line == $match[1])
3370
						echo '</pre><div style="background-color: #ffb0b5;"><pre style="margin: 0;">';
3371
3372
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
3373
					if (isset($data2[$line]) && $data2[$line] != '')
3374
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
3375
3376
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
3377
					{
3378
						$last_line = $color_match[1];
3379
						echo '</', substr($last_line, 1, 4), '>';
3380
					}
3381
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
3382
						$last_line = '';
3383
					elseif ($last_line != '' && $data2[$line] != '')
3384
						echo '</', substr($last_line, 1, 4), '>';
3385
3386
					if ($line == $match[1])
3387
						echo '</pre></div><pre style="margin: 0;">';
3388
					else
3389
						echo "\n";
3390
				}
3391
3392
				echo '</pre></div>';
3393
			}
3394
3395
			echo '
3396
	</body>
3397
</html>';
3398
		}
3399
3400
		die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
3401
	}
3402
}
3403
3404
/**
3405
 * Initialize a database connection.
3406
 */
3407
function loadDatabase()
3408
{
3409
	global $db_persist, $db_connection, $db_server, $db_user, $db_passwd;
3410
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $sourcedir, $db_prefix, $db_port, $db_mb4;
3411
3412
	// Figure out what type of database we are using.
3413
	if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
3414
		$db_type = 'mysql';
3415
3416
	// Load the file for the database.
3417
	require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
3418
3419
	$db_options = array();
3420
3421
	// Add in the port if needed
3422
	if (!empty($db_port))
3423
		$db_options['port'] = $db_port;
3424
3425
	if (!empty($db_mb4))
3426
		$db_options['db_mb4'] = $db_mb4;
3427
3428
	// 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.
3429
	if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
3430
	{
3431
		$options = array_merge($db_options, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true));
3432
3433
		$db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, $options);
3434
	}
3435
3436
	// Either we aren't in SSI mode, or it failed.
3437
	if (empty($db_connection))
3438
	{
3439
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
3440
3441
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
3442
	}
3443
3444
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
3445
	if (!$db_connection)
3446
		display_db_error();
3447
3448
	// If in SSI mode fix up the prefix.
3449
	if (SMF == 'SSI')
0 ignored issues
show
introduced by
The condition SMF == 'SSI' is always true.
Loading history...
3450
		db_fix_prefix($db_prefix, $db_name);
3451
}
3452
3453
/**
3454
 * Try to load up a supported caching method. This is saved in $cacheAPI if we are not overriding it.
3455
 *
3456
 * @param string $overrideCache Try to use a different cache method other than that defined in $cache_accelerator.
3457
 * @param bool $fallbackSMF Use the default SMF method if the accelerator fails.
3458
 * @return object|false A object of $cacheAPI, or False on failure.
3459
 */
3460
function loadCacheAccelerator($overrideCache = null, $fallbackSMF = true)
3461
{
3462
	global $sourcedir, $cacheAPI, $cache_accelerator, $cache_enable;
3463
3464
	// is caching enabled?
3465
	if (empty($cache_enable) && empty($overrideCache))
3466
		return false;
3467
3468
	// Not overriding this and we have a cacheAPI, send it back.
3469
	if (empty($overrideCache) && is_object($cacheAPI))
3470
		return $cacheAPI;
3471
	elseif (is_null($cacheAPI))
3472
		$cacheAPI = false;
3473
3474
	// Make sure our class is in session.
3475
	require_once($sourcedir . '/Class-CacheAPI.php');
3476
3477
	// What accelerator we are going to try.
3478
	$tryAccelerator = !empty($overrideCache) ? $overrideCache : (!empty($cache_accelerator) ? $cache_accelerator : 'smf');
3479
	$tryAccelerator = strtolower($tryAccelerator);
3480
3481
	// Do some basic tests.
3482
	if (file_exists($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php'))
3483
	{
3484
		require_once($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php');
3485
3486
		$cache_class_name = $tryAccelerator . '_cache';
3487
		$testAPI = new $cache_class_name();
3488
3489
		// No Support?  NEXT!
3490
		if (!$testAPI->isSupported())
3491
		{
3492
			// Can we save ourselves?
3493
			if (!empty($fallbackSMF) && is_null($overrideCache) && $tryAccelerator != 'smf')
3494
				return loadCacheAccelerator(null, false);
3495
			return false;
3496
		}
3497
3498
		// Connect up to the accelerator.
3499
		$testAPI->connect();
3500
3501
		// Don't set this if we are overriding the cache.
3502
		if (is_null($overrideCache))
3503
		{
3504
			$cacheAPI = $testAPI;
3505
			return $cacheAPI;
3506
		}
3507
		else
3508
			return $testAPI;
3509
	}
3510
}
3511
/**
3512
 * Load a cache entry, and if the cache entry could not be found, load
3513
 * a named file and call a function to get the relevant information.
3514
 *
3515
 * The cache-serving function must consist of an array,
3516
 * and contain some or all of the following:
3517
 *
3518
 * - `data`, required, the content of the item to be cached.
3519
 * - `expires`, required, the timestamp at which the item should expire
3520
 * - `refresh_eval`, optional, a string containing code to be evaluated that
3521
 *   returns a boolean that indicates whether or not to trigger a refresh.
3522
 * - `post_retri_eval`, optional, a string containing code to be
3523
 *   evaluated after the data has been updated and cached.
3524
 *
3525
 * Refresh the cache if either:
3526
 *
3527
 * - Caching is disabled.
3528
 * - The cache level isn't high enough.
3529
 * - The item has not been cached or the cached item expired.
3530
 * - The cached item has a custom expiration condition evaluating to true.
3531
 * - The expire time set in the cache item has passed (needed for Zend).
3532
 *
3533
 * @param string $key The key for this entry
3534
 * @param string $file The file associated with this entry
3535
 * @param callable $function The function to call
3536
 * @param array $params Parameters to be passed to the specified function
3537
 * @param int $level The cache level
3538
 * @return string The cached data
3539
 */
3540
function cache_quick_get($key, $file, $function, $params, $level = 1)
3541
{
3542
	global $modSettings, $sourcedir, $cache_enable;
3543
3544
	// @todo Why are we doing this if caching is disabled?
3545
3546
	if (function_exists('call_integration_hook'))
3547
		call_integration_hook('pre_cache_quick_get', array(&$key, &$file, &$function, &$params, &$level));
3548
3549
	/* Refresh the cache if either:
3550
		1. Caching is disabled.
3551
		2. The cache level isn't high enough.
3552
		3. The item has not been cached or the cached item expired.
3553
		4. The cached item has a custom expiration condition evaluating to true.
3554
		5. The expire time set in the cache item has passed (needed for Zend).
3555
	*/
3556
	if (empty($cache_enable) || $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
introduced by
The condition is_array($cache_block = ...e_get_data($key, 3600)) is always false.
Loading history...
introduced by
The use of eval() is discouraged.
Loading history...
3557
	{
3558
		require_once($sourcedir . '/' . $file);
3559
		$cache_block = call_user_func_array($function, $params);
3560
3561
		if (!empty($cache_enable) && $cache_enable >= $level)
3562
			cache_put_data($key, $cache_block, $cache_block['expires'] - time());
3563
	}
3564
3565
	// Some cached data may need a freshening up after retrieval.
3566
	if (!empty($cache_block['post_retri_eval']))
3567
		eval($cache_block['post_retri_eval']);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
3568
3569
	if (function_exists('call_integration_hook'))
3570
		call_integration_hook('post_cache_quick_get', array(&$cache_block));
3571
3572
	return $cache_block['data'];
3573
}
3574
3575
/**
3576
 * Puts value in the cache under key for ttl seconds.
3577
 *
3578
 * - It may "miss" so shouldn't be depended on
3579
 * - Uses the cache engine chosen in the ACP and saved in settings.php
3580
 * - It supports:
3581
 *	 Xcache: https://xcache.lighttpd.net/wiki/XcacheApi
3582
 *	 memcache: https://php.net/memcache
3583
 *	 APC: https://php.net/apc
3584
 *   APCu: https://php.net/book.apcu
3585
 *	 Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
3586
 *	 Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
3587
 *
3588
 * @param string $key A key for this value
3589
 * @param mixed $value The data to cache
3590
 * @param int $ttl How long (in seconds) the data should be cached for
3591
 */
3592
function cache_put_data($key, $value, $ttl = 120)
3593
{
3594
	global $smcFunc, $cache_enable, $cacheAPI;
3595
	global $cache_hits, $cache_count, $db_show_debug;
3596
3597
	if (empty($cache_enable) || empty($cacheAPI))
3598
		return;
3599
3600
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3601
	if (isset($db_show_debug) && $db_show_debug === true)
3602
	{
3603
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'put', 's' => $value === null ? 0 : strlen(isset($smcFunc['json_encode']) ? $smcFunc['json_encode']($value) : json_encode($value)));
3604
		$st = microtime(true);
3605
	}
3606
3607
	// The API will handle the rest.
3608
	$value = $value === null ? null : (isset($smcFunc['json_encode']) ? $smcFunc['json_encode']($value) : json_encode($value));
3609
	$cacheAPI->putData($key, $value, $ttl);
3610
3611
	if (function_exists('call_integration_hook'))
3612
		call_integration_hook('cache_put_data', array(&$key, &$value, &$ttl));
3613
3614
	if (isset($db_show_debug) && $db_show_debug === true)
3615
		$cache_hits[$cache_count]['t'] = microtime(true) - $st;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.
Loading history...
3616
}
3617
3618
/**
3619
 * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
3620
 *
3621
 * - It may often "miss", so shouldn't be depended on.
3622
 * - It supports the same as cache_put_data().
3623
 *
3624
 * @param string $key The key for the value to retrieve
3625
 * @param int $ttl The maximum age of the cached data
3626
 * @return string|null The cached data or null if nothing was loaded
3627
 */
3628
function cache_get_data($key, $ttl = 120)
3629
{
3630
	global $smcFunc, $cache_enable, $cacheAPI;
3631
	global $cache_hits, $cache_count, $cache_misses, $cache_count_misses, $db_show_debug;
3632
3633
	if (empty($cache_enable) || empty($cacheAPI))
3634
		return;
3635
3636
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3637
	if (isset($db_show_debug) && $db_show_debug === true)
3638
	{
3639
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'get');
3640
		$st = microtime(true);
3641
		$original_key = $key;
3642
	}
3643
3644
	// Ask the API to get the data.
3645
	$value = $cacheAPI->getData($key, $ttl);
3646
3647
	if (isset($db_show_debug) && $db_show_debug === true)
3648
	{
3649
		$cache_hits[$cache_count]['t'] = microtime(true) - $st;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.
Loading history...
3650
		$cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0;
3651
3652
		if (empty($value))
3653
		{
3654
			if (!is_array($cache_misses))
3655
				$cache_misses = array();
3656
3657
			$cache_count_misses = isset($cache_count_misses) ? $cache_count_misses + 1 : 1;
3658
			$cache_misses[$cache_count_misses] = array('k' => $original_key, 'd' => 'get');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $original_key does not seem to be defined for all execution paths leading up to this point.
Loading history...
3659
		}
3660
	}
3661
3662
	if (function_exists('call_integration_hook') && isset($value))
3663
		call_integration_hook('cache_get_data', array(&$key, &$ttl, &$value));
3664
3665
	return empty($value) ? null : (isset($smcFunc['json_decode']) ? $smcFunc['json_decode']($value, true) : smf_json_decode($value, true));
3666
}
3667
3668
/**
3669
 * Empty out the cache in use as best it can
3670
 *
3671
 * It may only remove the files of a certain type (if the $type parameter is given)
3672
 * Type can be user, data or left blank
3673
 * 	- user clears out user data
3674
 *  - data clears out system / opcode data
3675
 *  - If no type is specified will perform a complete cache clearing
3676
 * For cache engines that do not distinguish on types, a full cache flush will be done
3677
 *
3678
 * @param string $type The cache type ('memcached', 'apc', 'xcache', 'zend' or something else for SMF's file cache)
3679
 */
3680
function clean_cache($type = '')
3681
{
3682
	global $cacheAPI;
3683
3684
	// If we can't get to the API, can't do this.
3685
	if (empty($cacheAPI))
3686
		return;
3687
3688
	// Ask the API to do the heavy lifting. cleanCache also calls invalidateCache to be sure.
3689
	$cacheAPI->cleanCache($type);
3690
3691
	call_integration_hook('integrate_clean_cache');
3692
	clearstatcache();
3693
}
3694
3695
/**
3696
 * Helper function to set an array of data for an user's avatar.
3697
 *
3698
 * Makes assumptions based on the data provided, the following keys are required:
3699
 *
3700
 * - avatar The raw "avatar" column in members table
3701
 * - email The user's email. Used to get the gravatar info
3702
 * - filename The attachment filename
3703
 *
3704
 * @param array $data An array of raw info
3705
 * @return array An array of avatar data
3706
 */
3707
function set_avatar_data($data = array())
3708
{
3709
	global $modSettings, $smcFunc, $user_info;
3710
3711
	// Come on!
3712
	if (empty($data))
3713
		return array();
3714
3715
	// Set a nice default var.
3716
	$image = '';
3717
3718
	// Gravatar has been set as mandatory!
3719
	if (!empty($modSettings['gravatarOverride']))
3720
	{
3721
		if (!empty($modSettings['gravatarAllowExtraEmail']) && !empty($data['avatar']) && stristr($data['avatar'], 'gravatar://'))
3722
			$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3723
3724
		elseif (!empty($data['email']))
3725
			$image = get_gravatar_url($data['email']);
3726
	}
3727
3728
	// Look if the user has a gravatar field or has set an external url as avatar.
3729
	else
3730
	{
3731
		// So it's stored in the member table?
3732
		if (!empty($data['avatar']))
3733
		{
3734
			// Gravatar.
3735
			if (stristr($data['avatar'], 'gravatar://'))
3736
			{
3737
				if ($data['avatar'] == 'gravatar://')
3738
					$image = get_gravatar_url($data['email']);
3739
3740
				elseif (!empty($modSettings['gravatarAllowExtraEmail']))
3741
					$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3742
			}
3743
3744
			// External url.
3745
			else
3746
				$image = parse_url($data['avatar'], PHP_URL_SCHEME) !== null ? get_proxied_url($data['avatar']) : $modSettings['avatar_url'] . '/' . $data['avatar'];
3747
		}
3748
3749
		// Perhaps this user has an attachment as avatar...
3750
		elseif (!empty($data['filename']))
3751
			$image = $modSettings['custom_avatar_url'] . '/' . $data['filename'];
3752
3753
		// Right... no avatar... use our default image.
3754
		else
3755
			$image = $modSettings['avatar_url'] . '/default.png';
3756
	}
3757
3758
	call_integration_hook('integrate_set_avatar_data', array(&$image, &$data));
3759
3760
	// 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.
3761
	if (!empty($image))
3762
		return array(
3763
			'name' => !empty($data['avatar']) ? $data['avatar'] : '',
3764
			'image' => '<img class="avatar" src="' . $image . '" alt="">',
3765
			'href' => $image,
3766
			'url' => $image,
3767
		);
3768
3769
	// Fallback to make life easier for everyone...
3770
	else
3771
		return array(
3772
			'name' => '',
3773
			'image' => '',
3774
			'href' => '',
3775
			'url' => '',
3776
		);
3777
}
3778
3779
/**
3780
 * Gets, and if necessary creates, the authentication secret to use for cookies, tokens, etc.
3781
 *
3782
 * Note: Never use the $auth_secret variable directly. Always call this function instead.
3783
 *
3784
 * @return string The authentication secret.
3785
 */
3786
function get_auth_secret()
3787
{
3788
	global $auth_secret, $sourcedir, $smcFunc;
3789
3790
	if (empty($auth_secret))
3791
	{
3792
		$auth_secret = bin2hex($smcFunc['random_bytes'](32));
3793
3794
		// It is important to store this in Settings.php, not the database.
3795
		require_once($sourcedir . '/Subs-Admin.php');
3796
		updateSettingsFile(array('auth_secret' => $auth_secret));
3797
	}
3798
3799
	return $auth_secret;
3800
}
3801
3802
?>