Passed
Pull Request — release-2.1 (#6262)
by Jeremy
03:45
created

clean_cache()   A

Complexity

Conditions 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nop 1
dl 0
loc 13
rs 10
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 RC3
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
			return $string;
110
		} : function($string)
111
		{
112
			return $string;
113
		};
114
	$fix_utf8mb4 = function($string) use ($utf8, $smcFunc)
115
	{
116
		if (!$utf8 || $smcFunc['db_mb4'])
117
			return $string;
118
119
		$i = 0;
120
		$len = strlen($string);
121
		$new_string = '';
122
		while ($i < $len)
123
		{
124
			$ord = ord($string[$i]);
125
			if ($ord < 128)
126
			{
127
				$new_string .= $string[$i];
128
				$i++;
129
			}
130
			elseif ($ord < 224)
131
			{
132
				$new_string .= $string[$i] . $string[$i + 1];
133
				$i += 2;
134
			}
135
			elseif ($ord < 240)
136
			{
137
				$new_string .= $string[$i] . $string[$i + 1] . $string[$i + 2];
138
				$i += 3;
139
			}
140
			elseif ($ord < 248)
141
			{
142
				// Magic happens.
143
				$val = (ord($string[$i]) & 0x07) << 18;
144
				$val += (ord($string[$i + 1]) & 0x3F) << 12;
145
				$val += (ord($string[$i + 2]) & 0x3F) << 6;
146
				$val += (ord($string[$i + 3]) & 0x3F);
147
				$new_string .= '&#' . $val . ';';
148
				$i += 4;
149
			}
150
		}
151
		return $new_string;
152
	};
153
154
	// global array of anonymous helper functions, used mostly to properly handle multi byte strings
155
	$smcFunc += array(
156
		'entity_fix' => function($string)
157
		{
158
			$num = $string[0] === 'x' ? hexdec(substr($string, 1)) : (int) $string;
159
			return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? '' : '&#' . $num . ';';
160
		},
161
		'htmlspecialchars' => function($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') use ($ent_check, $utf8, $fix_utf8mb4)
162
		{
163
			return $fix_utf8mb4($ent_check(htmlspecialchars($string, $quote_style, $utf8 ? 'UTF-8' : $charset)));
164
		},
165
		'htmltrim' => function($string) use ($utf8, $ent_check)
166
		{
167
			// Preg_replace space characters depend on the character set in use
168
			$space_chars = $utf8 ? '\p{Z}\p{C}' : '\x00-\x20\x80-\xA0';
169
170
			return preg_replace('~^(?:[' . $space_chars . ']|&nbsp;)+|(?:[' . $space_chars . ']|&nbsp;)+$~' . ($utf8 ? 'u' : ''), '', $ent_check($string));
171
		},
172
		'strlen' => function($string) use ($ent_list, $utf8, $ent_check)
173
		{
174
			return strlen(preg_replace('~' . $ent_list . ($utf8 ? '|.~u' : '~'), '_', $ent_check($string)));
175
		},
176
		'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...
177
		{
178
			$haystack_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : ''), $ent_check($haystack), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
179
180
			if (strlen($needle) === 1)
181
			{
182
				$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

182
				$result = array_search($needle, array_slice(/** @scrutinizer ignore-type */ $haystack_arr, $offset));
Loading history...
183
				return is_int($result) ? $result + $offset : false;
184
			}
185
			else
186
			{
187
				$needle_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($needle), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
188
				$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

188
				$needle_size = count(/** @scrutinizer ignore-type */ $needle_arr);
Loading history...
189
190
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
191
				while ((int) $result === $result)
192
				{
193
					$offset += $result;
194
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
195
						return $offset;
196
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
197
				}
198
				return false;
199
			}
200
		},
201
		'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...
202
		{
203
			$ent_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($string), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
204
			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

204
			return $length === null ? implode('', array_slice(/** @scrutinizer ignore-type */ $ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
Loading history...
205
		},
206
		'strtolower' => $utf8 ? function($string) use ($sourcedir)
207
		{
208
			if (!function_exists('mb_strtolower'))
209
			{
210
				require_once($sourcedir . '/Subs-Charset.php');
211
				return utf8_strtolower($string);
212
			}
213
214
			return mb_strtolower($string, 'UTF-8');
215
		} : 'strtolower',
216
		'strtoupper' => $utf8 ? function($string)
217
		{
218
			global $sourcedir;
219
220
			if (!function_exists('mb_strtolower'))
221
			{
222
				require_once($sourcedir . '/Subs-Charset.php');
223
				return utf8_strtoupper($string);
224
			}
225
226
			return mb_strtoupper($string, 'UTF-8');
227
		} : 'strtoupper',
228
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
229
		{
230
			$string = $ent_check($string);
231
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~' . ($utf8 ? 'u' : ''), $string, $matches);
232
			$string = $matches[0];
233
			while (strlen($string) > $length)
234
				$string = preg_replace('~(?:' . $ent_list . '|.)$~' . ($utf8 ? 'u' : ''), '', $string);
235
			return $string;
236
		},
237
		'ucfirst' => $utf8 ? function($string) use (&$smcFunc)
238
		{
239
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
240
		} : 'ucfirst',
241
		'ucwords' => $utf8 ? function($string) use (&$smcFunc)
242
		{
243
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
244
			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

244
			for ($i = 0, $n = count(/** @scrutinizer ignore-type */ $words); $i < $n; $i += 2)
Loading history...
245
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
246
			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

246
			return implode('', /** @scrutinizer ignore-type */ $words);
Loading history...
247
		} : 'ucwords',
248
		'json_decode' => 'smf_json_decode',
249
		'json_encode' => 'json_encode',
250
		'random_int' => function($min = 0, $max = PHP_INT_MAX)
251
		{
252
			global $sourcedir;
253
254
			// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
255
			if (!is_callable('random_int'))
256
				require_once($sourcedir . '/random_compat/random.php');
257
258
			return random_int($min, $max);
259
		},
260
		'random_bytes' => function($length = 64)
261
		{
262
			global $sourcedir;
263
264
			if (!is_callable('random_bytes'))
265
				require_once($sourcedir . '/random_compat/random.php');
266
267
			// Make sure length is valid
268
			$length = max(1, (int) $length);
269
270
			return random_bytes($length);
271
		},
272
	);
273
274
	// Setting the timezone is a requirement for some functions.
275
	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

275
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], /** @scrutinizer ignore-type */ timezone_identifiers_list()))
Loading history...
276
		date_default_timezone_set($modSettings['default_timezone']);
277
	else
278
	{
279
		// Get PHP's default timezone, if set
280
		$ini_tz = ini_get('date.timezone');
281
		if (!empty($ini_tz))
282
			$modSettings['default_timezone'] = $ini_tz;
283
		else
284
			$modSettings['default_timezone'] = '';
285
286
		// If date.timezone is unset, invalid, or just plain weird, make a best guess
287
		if (!in_array($modSettings['default_timezone'], timezone_identifiers_list()))
288
		{
289
			$server_offset = @mktime(0, 0, 0, 1, 1, 1970);
290
			$modSettings['default_timezone'] = timezone_name_from_abbr('', $server_offset, 0);
291
		}
292
293
		date_default_timezone_set($modSettings['default_timezone']);
294
	}
295
296
	// Check the load averages?
297
	if (!empty($modSettings['loadavg_enable']))
298
	{
299
		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...
300
		{
301
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
302
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
303
				$modSettings['load_average'] = (float) $matches[1];
304
			elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0)
305
				$modSettings['load_average'] = (float) $matches[1];
306
			else
307
				unset($modSettings['load_average']);
308
309
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
310
				cache_put_data('loadavg', $modSettings['load_average'], 90);
311
		}
312
313
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
314
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
315
316
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
317
			display_loadavg_error();
318
	}
319
320
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
321
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
322
323
	// Here to justify the name of this function. :P
324
	// It should be added to the install and upgrade scripts.
325
	// But since the converters need to be updated also. This is easier.
326
	if (empty($modSettings['currentAttachmentUploadDir']))
327
	{
328
		updateSettings(array(
329
			'attachmentUploadDir' => $smcFunc['json_encode'](array(1 => $modSettings['attachmentUploadDir'])),
330
			'currentAttachmentUploadDir' => 1,
331
		));
332
	}
333
334
	// Respect PHP's limits.
335
	$post_max_kb = floor(memoryReturnBytes(ini_get('post_max_size')) / 1024);
336
	$file_max_kb = floor(memoryReturnBytes(ini_get('upload_max_filesize')) / 1024);
337
	$modSettings['attachmentPostLimit'] = empty($modSettings['attachmentPostLimit']) ? $post_max_kb : min($modSettings['attachmentPostLimit'], $post_max_kb);
338
	$modSettings['attachmentSizeLimit'] = empty($modSettings['attachmentSizeLimit']) ? $file_max_kb : min($modSettings['attachmentSizeLimit'], $file_max_kb);
339
340
	// Integration is cool.
341
	if (defined('SMF_INTEGRATION_SETTINGS'))
342
	{
343
		$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...
344
		foreach ($integration_settings as $hook => $function)
345
			add_integration_function($hook, $function, false);
346
	}
347
348
	// Any files to pre include?
349
	if (!empty($modSettings['integrate_pre_include']))
350
	{
351
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
352
		foreach ($pre_includes as $include)
353
		{
354
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
355
			if (file_exists($include))
356
				require_once($include);
357
		}
358
	}
359
360
	// This determines the server... not used in many places, except for login fixing.
361
	$context['server'] = array(
362
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
363
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
364
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
365
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
366
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
367
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
368
		'is_windows' => DIRECTORY_SEPARATOR === '\\',
369
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
370
	);
371
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
372
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
373
374
	// Define a list of icons used across multiple places.
375
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
376
377
	// Define an array for custom profile fields placements.
378
	$context['cust_profile_fields_placement'] = array(
379
		'standard',
380
		'icons',
381
		'above_signature',
382
		'below_signature',
383
		'below_avatar',
384
		'above_member',
385
		'bottom_poster',
386
		'before_member',
387
		'after_member',
388
	);
389
390
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
391
	$context['meta_tags'] = array();
392
393
	// Define an array of allowed HTML tags.
394
	$context['allowed_html_tags'] = array(
395
		'<img>',
396
		'<div>',
397
	);
398
399
	// These are the only valid image types for SMF attachments, by default anyway.
400
	// Note: The values are for image mime types, not file extensions.
401
	$context['valid_image_types'] = array(
402
		IMAGETYPE_GIF => 'gif',
403
		IMAGETYPE_JPEG => 'jpeg',
404
		IMAGETYPE_PNG => 'png',
405
		IMAGETYPE_PSD => 'psd',
406
		IMAGETYPE_BMP => 'bmp',
407
		IMAGETYPE_TIFF_II => 'tiff',
408
		IMAGETYPE_TIFF_MM => 'tiff',
409
		IMAGETYPE_IFF => 'iff'
410
	);
411
412
	// Define a list of allowed tags for descriptions.
413
	$context['description_allowed_tags'] = array(
414
		'abbr', 'anchor', 'b', 'center', 'color', 'font', 'hr', 'i', 'img',
415
		'iurl', 'left', 'li', 'list', 'ltr', 'pre', 'right', 's', 'sub',
416
		'sup', 'table', 'td', 'tr', 'u', 'url',
417
	);
418
419
	// Define a list of deprecated BBC tags
420
	// Even when enabled, they'll only work in old posts and not new ones
421
	$context['legacy_bbc'] = array(
422
		'acronym', 'bdo', 'black', 'blue', 'flash', 'ftp', 'glow',
423
		'green', 'move', 'red', 'shadow', 'tt', 'white',
424
	);
425
426
	// Define a list of BBC tags that require permissions to use
427
	$context['restricted_bbc'] = array(
428
		'html',
429
	);
430
431
	// Call pre load integration functions.
432
	call_integration_hook('integrate_pre_load');
433
}
434
435
/**
436
 * Load all the important user information.
437
 * What it does:
438
 * 	- sets up the $user_info array
439
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
440
 * 	- first checks for cookie or integration validation.
441
 * 	- uses the current session if no integration function or cookie is found.
442
 * 	- checks password length, if member is activated and the login span isn't over.
443
 * 		- if validation fails for the user, $id_member is set to 0.
444
 * 		- updates the last visit time when needed.
445
 */
446
function loadUserSettings()
447
{
448
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
449
	global $cookiename, $user_info, $language, $context, $cache_enable;
450
451
	require_once($sourcedir . '/Subs-Auth.php');
452
453
	// Check first the integration, then the cookie, and last the session.
454
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
455
	{
456
		$id_member = 0;
457
		foreach ($integration_ids as $integration_id)
458
		{
459
			$integration_id = (int) $integration_id;
460
			if ($integration_id > 0)
461
			{
462
				$id_member = $integration_id;
463
				$already_verified = true;
464
				break;
465
			}
466
		}
467
	}
468
	else
469
		$id_member = 0;
470
471
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
472
	{
473
		// First try 2.1 json-format cookie
474
		$cookie_data = $smcFunc['json_decode']($_COOKIE[$cookiename], true, false);
475
476
		// Legacy format (for recent 2.0 --> 2.1 upgrades)
477
		if (empty($cookie_data))
478
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
479
480
		list($id_member, $password, $login_span, $cookie_domain, $cookie_path) = array_pad((array) $cookie_data, 5, '');
481
482
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
483
484
		// Make sure the cookie is set to the correct domain and path
485
		if (array($cookie_domain, $cookie_path) !== url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])))
486
			setLoginCookie((int) $login_span - time(), $id_member);
487
	}
488
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
489
	{
490
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
491
		$cookie_data = $smcFunc['json_decode']($_SESSION['login_' . $cookiename], true);
492
493
		if (empty($cookie_data))
494
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
495
496
		list($id_member, $password, $login_span) = array_pad((array) $cookie_data, 3, '');
497
		$id_member = !empty($id_member) && strlen($password) == 40 && (int) $login_span > time() ? (int) $id_member : 0;
498
	}
499
500
	// Only load this stuff if the user isn't a guest.
501
	if ($id_member != 0)
502
	{
503
		// Is the member data cached?
504
		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...
505
		{
506
			$request = $smcFunc['db_query']('', '
507
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
508
				FROM {db_prefix}members AS mem
509
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
510
				WHERE mem.id_member = {int:id_member}
511
				LIMIT 1',
512
				array(
513
					'id_member' => $id_member,
514
				)
515
			);
516
			$user_settings = $smcFunc['db_fetch_assoc']($request);
517
			$smcFunc['db_free_result']($request);
518
519
			if (!empty($user_settings['avatar']))
520
				$user_settings['avatar'] = get_proxied_url($user_settings['avatar']);
521
522
			if (!empty($cache_enable) && $cache_enable >= 2)
523
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
524
		}
525
526
		// Did we find 'im?  If not, junk it.
527
		if (!empty($user_settings))
528
		{
529
			// As much as the password should be right, we can assume the integration set things up.
530
			if (!empty($already_verified) && $already_verified === true)
531
				$check = true;
532
			// SHA-512 hash should be 128 characters long.
533
			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...
534
				$check = hash_equals(hash_salt($user_settings['passwd'], $user_settings['password_salt']), $password);
535
			else
536
				$check = false;
537
538
			// Wrong password or not activated - either way, you're going nowhere.
539
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
540
		}
541
		else
542
			$id_member = 0;
543
544
		// Check if we are forcing TFA
545
		$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...
546
547
		// Don't force TFA on popups
548
		if ($force_tfasetup)
0 ignored issues
show
introduced by
The condition $force_tfasetup is always false.
Loading history...
549
		{
550
			if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'profile' && isset($_REQUEST['area']) && in_array($_REQUEST['area'], array('popup', 'alerts_popup')))
551
				$force_tfasetup = false;
552
			elseif (isset($_REQUEST['action']) && $_REQUEST['action'] == 'pm' && (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'popup'))
553
				$force_tfasetup = false;
554
555
			call_integration_hook('integrate_force_tfasetup', array(&$force_tfasetup));
556
		}
557
558
		// If we no longer have the member maybe they're being all hackey, stop brute force!
559
		if (!$id_member)
560
		{
561
			require_once($sourcedir . '/LogInOut.php');
562
			validatePasswordFlood(
563
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
564
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
565
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
566
				$id_member != 0
567
			);
568
		}
569
		// Validate for Two Factor Authentication
570
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
571
		{
572
			$tfacookie = $cookiename . '_tfa';
573
			$tfasecret = null;
574
575
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
576
577
			if (empty($verified) || !in_array(true, $verified))
578
			{
579
				if (!empty($_COOKIE[$tfacookie]))
580
				{
581
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie], true);
582
583
					list ($tfamember, $tfasecret) = array_pad((array) $tfa_data, 2, '');
584
585
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
586
						$tfasecret = null;
587
				}
588
589
				// They didn't finish logging in before coming here? Then they're no one to us.
590
				if (empty($tfasecret) || !hash_equals(hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']), $tfasecret))
591
				{
592
					setLoginCookie(-3600, $id_member);
593
					$id_member = 0;
594
					$user_settings = array();
595
				}
596
			}
597
		}
598
		// When authenticating their two factor code, make sure to reset their ID for security
599
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
600
		{
601
			$id_member = 0;
602
			$context['tfa_member'] = $user_settings;
603
			$user_settings = array();
604
		}
605
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
606
		elseif ($force_tfasetup)
0 ignored issues
show
introduced by
The condition $force_tfasetup is always false.
Loading history...
607
		{
608
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
609
			{
610
				//Build an array of ALL user membergroups.
611
				$full_groups = array($user_settings['id_group']);
612
				if (!empty($user_settings['additional_groups']))
613
				{
614
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
615
					$full_groups = array_unique($full_groups); //duplicates, maybe?
616
				}
617
618
				//Find out if any group requires 2FA
619
				$request = $smcFunc['db_query']('', '
620
					SELECT COUNT(id_group) AS total
621
					FROM {db_prefix}membergroups
622
					WHERE tfa_required = {int:tfa_required}
623
						AND id_group IN ({array_int:full_groups})',
624
					array(
625
						'tfa_required' => 1,
626
						'full_groups' => $full_groups,
627
					)
628
				);
629
				$row = $smcFunc['db_fetch_assoc']($request);
630
				$smcFunc['db_free_result']($request);
631
			}
632
			else
633
				$row['total'] = 1; //simplifies logics in the next "if"
0 ignored issues
show
Comprehensibility Best Practice introduced by
$row was never initialized. Although not strictly required by PHP, it is generally a good practice to add $row = array(); before regardless.
Loading history...
634
635
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
636
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
637
638
			if ($row['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: ($row['total'] > 0 && ! ... && $area != 'tfasetup', Probably Intended Meaning: $row['total'] > 0 && (! ...&& $area != 'tfasetup')
Loading history...
639
				redirectexit('action=profile;area=tfasetup;forced');
640
		}
641
	}
642
643
	// Found 'im, let's set up the variables.
644
	if ($id_member != 0)
645
	{
646
		// Let's not update the last visit time in these cases...
647
		// 1. SSI doesn't count as visiting the forum.
648
		// 2. RSS feeds and XMLHTTP requests don't count either.
649
		// 3. If it was set within this session, no need to set it again.
650
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
651
		// 5. We're still logging in or authenticating
652
		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...
653
		{
654
			// @todo can this be cached?
655
			// Do a quick query to make sure this isn't a mistake.
656
			$result = $smcFunc['db_query']('', '
657
				SELECT poster_time
658
				FROM {db_prefix}messages
659
				WHERE id_msg = {int:id_msg}
660
				LIMIT 1',
661
				array(
662
					'id_msg' => $user_settings['id_msg_last_visit'],
663
				)
664
			);
665
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
666
			$smcFunc['db_free_result']($result);
667
668
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
669
670
			// If it was *at least* five hours ago...
671
			if ($visitTime < time() - 5 * 3600)
672
			{
673
				updateMemberData($id_member, array('id_msg_last_visit' => (int) $modSettings['maxMsgID'], 'last_login' => time(), 'member_ip' => $_SERVER['REMOTE_ADDR'], 'member_ip2' => $_SERVER['BAN_CHECK_IP']));
674
				$user_settings['last_login'] = time();
675
676
				if (!empty($cache_enable) && $cache_enable >= 2)
677
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
678
679
				if (!empty($cache_enable))
680
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
681
			}
682
		}
683
		elseif (empty($_SESSION['id_msg_last_visit']))
684
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
685
686
		$username = $user_settings['member_name'];
687
688
		if (empty($user_settings['additional_groups']))
689
			$user_info = array(
690
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
691
			);
692
693
		else
694
			$user_info = array(
695
				'groups' => array_merge(
696
					array($user_settings['id_group'], $user_settings['id_post_group']),
697
					explode(',', $user_settings['additional_groups'])
698
				)
699
			);
700
701
		// Because history has proven that it is possible for groups to go bad - clean up in case.
702
		$user_info['groups'] = array_map('intval', $user_info['groups']);
703
704
		// This is a logged in user, so definitely not a spider.
705
		$user_info['possibly_robot'] = false;
706
707
		// Figure out the new time offset.
708
		if (!empty($user_settings['timezone']))
709
		{
710
			// Get the offsets from UTC for the server, then for the user.
711
			$tz_system = new DateTimeZone(@date_default_timezone_get());
712
			$tz_user = new DateTimeZone($user_settings['timezone']);
713
			$time_system = new DateTime('now', $tz_system);
714
			$time_user = new DateTime('now', $tz_user);
715
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
716
		}
717
		else
718
		{
719
			// !!! Compatibility.
720
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'];
721
		}
722
	}
723
	// If the user is a guest, initialize all the critical user settings.
724
	else
725
	{
726
		// This is what a guest's variables should be.
727
		$username = '';
728
		$user_info = array('groups' => array(-1));
729
		$user_settings = array();
730
731
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
732
			$_COOKIE[$cookiename] = '';
733
734
		// Expire the 2FA cookie
735
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
736
		{
737
			$tfa_data = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
738
739
			list (,, $exp) = array_pad((array) $tfa_data, 3, 0);
740
741
			if (time() > $exp)
742
			{
743
				$_COOKIE[$cookiename . '_tfa'] = '';
744
				setTFACookie(-3600, 0, '');
745
			}
746
		}
747
748
		// Create a login token if it doesn't exist yet.
749
		if (!isset($_SESSION['token']['post-login']))
750
			createToken('login');
751
		else
752
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
753
754
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
755
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
756
		{
757
			require_once($sourcedir . '/ManageSearchEngines.php');
758
			$user_info['possibly_robot'] = SpiderCheck();
759
		}
760
		elseif (!empty($modSettings['spider_mode']))
761
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
762
		// If we haven't turned on proper spider hunts then have a guess!
763
		else
764
		{
765
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
766
			$user_info['possibly_robot'] = (strpos($_SERVER['HTTP_USER_AGENT'], 'Mozilla') === false && strpos($_SERVER['HTTP_USER_AGENT'], 'Opera') === false) || strpos($ci_user_agent, 'googlebot') !== false || strpos($ci_user_agent, 'slurp') !== false || strpos($ci_user_agent, 'crawl') !== false || strpos($ci_user_agent, 'bingbot') !== false || strpos($ci_user_agent, 'bingpreview') !== false || strpos($ci_user_agent, 'adidxbot') !== false || strpos($ci_user_agent, 'msnbot') !== false;
767
		}
768
769
		// We don't know the offset...
770
		$user_info['time_offset'] = 0;
771
	}
772
773
	// Login Cookie times. Format: time => txt
774
	$context['login_cookie_times'] = array(
775
		3153600 => 'always_logged_in',
776
		60 => 'one_hour',
777
		1440 => 'one_day',
778
		10080 => 'one_week',
779
		43200 => 'one_month',
780
	);
781
782
	// Set up the $user_info array.
783
	$user_info += array(
784
		'id' => $id_member,
785
		'username' => $username,
786
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
787
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
788
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
789
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
790
		'is_guest' => $id_member == 0,
791
		'is_admin' => in_array(1, $user_info['groups']),
792
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
793
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
794
		'ip' => $_SERVER['REMOTE_ADDR'],
795
		'ip2' => $_SERVER['BAN_CHECK_IP'],
796
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
797
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
798
		'avatar' => array(
799
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
800
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
801
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
802
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
803
		),
804
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
805
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
806
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
807
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
808
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
809
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
810
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
811
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
812
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
813
		'permissions' => array(),
814
	);
815
	$user_info['groups'] = array_unique($user_info['groups']);
816
	$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);
817
818
	// Make sure that the last item in the ignore boards array is valid. If the list was too long it could have an ending comma that could cause problems.
819
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
820
		unset($user_info['ignoreboards'][$tmp]);
821
822
	// Allow the user to change their language.
823
	if (!empty($modSettings['userLanguage']))
824
	{
825
		$languages = getLanguages();
826
827
		// Is it valid?
828
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
829
		{
830
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
831
832
			// Make it permanent for members.
833
			if (!empty($user_info['id']))
834
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
835
			else
836
				$_SESSION['language'] = $user_info['language'];
837
		}
838
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
839
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
840
	}
841
842
	$temp = build_query_board($user_info['id']);
843
	$user_info['query_see_board'] = $temp['query_see_board'];
844
	$user_info['query_see_message_board'] = $temp['query_see_message_board'];
845
	$user_info['query_see_topic_board'] = $temp['query_see_topic_board'];
846
	$user_info['query_wanna_see_board'] = $temp['query_wanna_see_board'];
847
	$user_info['query_wanna_see_message_board'] = $temp['query_wanna_see_message_board'];
848
	$user_info['query_wanna_see_topic_board'] = $temp['query_wanna_see_topic_board'];
849
850
	call_integration_hook('integrate_user_info');
851
}
852
853
/**
854
 * Load minimal user info from members table.
855
 * Intended for use by background tasks that need to populate $user_info.
856
 *
857
 * @param int|array $user_ids The users IDs to get the data for.
858
 * @return array
859
 * @throws Exception
860
 */
861
function loadMinUserInfo($user_ids = array())
862
{
863
	global $smcFunc, $modSettings, $language;
864
	static $user_info_min = array();
865
866
	$user_ids = (array) $user_ids;
867
868
	// Already loaded?
869
	if (!empty($user_ids))
870
		$user_ids = array_diff($user_ids, array_keys($user_info_min));
871
872
	if (empty($user_ids))
873
		return $user_info_min;
874
875
	$columns_to_load = array(
876
		'id_member',
877
		'member_name',
878
		'real_name',
879
		'time_offset',
880
		'additional_groups',
881
		'id_group',
882
		'id_post_group',
883
		'lngfile',
884
		'smiley_set',
885
		'timezone',
886
	);
887
888
	call_integration_hook('integrate_load_min_user_settings_columns', array(&$columns_to_load));
889
890
	$request = $smcFunc['db_query']('', '
891
		SELECT {raw:columns}
892
		FROM {db_prefix}members
893
		WHERE id_member IN ({array_int:user_ids})',
894
		array(
895
			'user_ids' => array_map('intval', array_unique($user_ids)),
896
			'columns' => implode(', ', $columns_to_load)
897
		)
898
	);
899
900
	while ($row = $smcFunc['db_fetch_assoc']($request))
901
	{
902
		$user_info_min[$row['id_member']] = array(
903
			'id' => $row['id_member'],
904
			'username' => $row['member_name'],
905
			'name' => isset($row['real_name']) ? $row['real_name'] : '',
906
			'language' => (empty($row['lngfile']) || empty($modSettings['userLanguage'])) ? $language : $row['lngfile'],
907
			'is_guest' => false,
908
			'time_format' => empty($row['time_format']) ? $modSettings['time_format'] : $row['time_format'],
909
			'smiley_set' => empty($row['smiley_set']) ? $modSettings['smiley_sets_default'] : $row['smiley_set'],
910
		);
911
912
		if (empty($row['additional_groups']))
913
			$user_info_min[$row['id_member']]['groups'] = array($row['id_group'], $row['id_post_group']);
914
915
		else
916
			$user_info_min[$row['id_member']]['groups'] = array_merge(
917
				array($row['id_group'], $row['id_post_group']),
918
				explode(',', $row['additional_groups'])
919
			);
920
921
		$user_info_min[$row['id_member']]['is_admin'] = in_array(1, $user_info_min[$row['id_member']]['groups']);
922
923
		if (!empty($row['timezone']))
924
		{
925
			$tz_system = new \DateTimeZone(@date_default_timezone_get());
926
			$tz_user = new \DateTimeZone($row['timezone']);
927
			$time_system = new \DateTime('now', $tz_system);
928
			$time_user = new \DateTime('now', $tz_user);
929
			$user_info_min[$row['id_member']]['time_offset'] = ($tz_user->getOffset($time_user) -
930
					$tz_system->getOffset($time_system)) / 3600;
931
		}
932
933
		else
934
			$user_info_min[$row['id_member']]['time_offset'] = empty($row['time_offset']) ? 0 : $row['time_offset'];
935
	}
936
937
	$smcFunc['db_free_result']($request);
938
939
	call_integration_hook('integrate_load_min_user_settings', array(&$user_info_min));
940
941
	return $user_info_min;
942
}
943
944
/**
945
 * Check for moderators and see if they have access to the board.
946
 * What it does:
947
 * - sets up the $board_info array for current board information.
948
 * - if cache is enabled, the $board_info array is stored in cache.
949
 * - redirects to appropriate post if only message id is requested.
950
 * - is only used when inside a topic or board.
951
 * - determines the local moderators for the board.
952
 * - adds group id 3 if the user is a local moderator for the board they are in.
953
 * - prevents access if user is not in proper group nor a local moderator of the board.
954
 */
955
function loadBoard()
956
{
957
	global $txt, $scripturl, $context, $modSettings;
958
	global $board_info, $board, $topic, $user_info, $smcFunc, $cache_enable;
959
960
	// Assume they are not a moderator.
961
	$user_info['is_mod'] = false;
962
	$context['user']['is_mod'] = &$user_info['is_mod'];
963
964
	// Start the linktree off empty..
965
	$context['linktree'] = array();
966
967
	// Have they by chance specified a message id but nothing else?
968
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
969
	{
970
		// Make sure the message id is really an int.
971
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
972
973
		// Looking through the message table can be slow, so try using the cache first.
974
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
975
		{
976
			$request = $smcFunc['db_query']('', '
977
				SELECT id_topic
978
				FROM {db_prefix}messages
979
				WHERE id_msg = {int:id_msg}
980
				LIMIT 1',
981
				array(
982
					'id_msg' => $_REQUEST['msg'],
983
				)
984
			);
985
986
			// So did it find anything?
987
			if ($smcFunc['db_num_rows']($request))
988
			{
989
				list ($topic) = $smcFunc['db_fetch_row']($request);
990
				$smcFunc['db_free_result']($request);
991
				// Save save save.
992
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
993
			}
994
		}
995
996
		// Remember redirection is the key to avoiding fallout from your bosses.
997
		if (!empty($topic))
998
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
999
		else
1000
		{
1001
			loadPermissions();
1002
			loadTheme();
1003
			fatal_lang_error('topic_gone', false);
1004
		}
1005
	}
1006
1007
	// Load this board only if it is specified.
1008
	if (empty($board) && empty($topic))
1009
	{
1010
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
1011
		return;
1012
	}
1013
1014
	if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1015
	{
1016
		// @todo SLOW?
1017
		if (!empty($topic))
1018
			$temp = cache_get_data('topic_board-' . $topic, 120);
1019
		else
1020
			$temp = cache_get_data('board-' . $board, 120);
1021
1022
		if (!empty($temp))
1023
		{
1024
			$board_info = $temp;
1025
			$board = $board_info['id'];
1026
		}
1027
	}
1028
1029
	if (empty($temp))
1030
	{
1031
		$custom_column_selects = [];
1032
		$custom_column_parameters = [
1033
			'current_topic' => $topic,
1034
			'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
1035
		];
1036
1037
		call_integration_hook('integrate_load_board', array(&$custom_column_selects, &$custom_column_parameters));
1038
1039
		$request = $smcFunc['db_query']('load_board_info', '
1040
			SELECT
1041
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
1042
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
1043
				COALESCE(mem.id_member, 0) AS id_moderator,
1044
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
1045
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
1046
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
1047
				' . (!empty($custom_column_selects) ? (', ' . implode(', ', $custom_column_selects)) : '') . '
1048
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
1049
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
1050
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1051
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
1052
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
1053
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
1054
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
1055
			WHERE b.id_board = {raw:board_link}',
1056
			$custom_column_parameters
1057
		);
1058
1059
		// If there aren't any, skip.
1060
		if ($smcFunc['db_num_rows']($request) > 0)
1061
		{
1062
			$row = $smcFunc['db_fetch_assoc']($request);
1063
1064
			// Set the current board.
1065
			if (!empty($row['id_board']))
1066
				$board = $row['id_board'];
1067
1068
			// Basic operating information. (globals... :/)
1069
			$board_info = array(
1070
				'id' => $board,
1071
				'moderators' => array(),
1072
				'moderator_groups' => array(),
1073
				'cat' => array(
1074
					'id' => $row['id_cat'],
1075
					'name' => $row['cname']
1076
				),
1077
				'name' => $row['bname'],
1078
				'description' => $row['description'],
1079
				'num_topics' => $row['num_topics'],
1080
				'unapproved_topics' => $row['unapproved_topics'],
1081
				'unapproved_posts' => $row['unapproved_posts'],
1082
				'unapproved_user_topics' => 0,
1083
				'parent_boards' => getBoardParents($row['id_parent']),
1084
				'parent' => $row['id_parent'],
1085
				'child_level' => $row['child_level'],
1086
				'theme' => $row['id_theme'],
1087
				'override_theme' => !empty($row['override_theme']),
1088
				'profile' => $row['id_profile'],
1089
				'redirect' => $row['redirect'],
1090
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
1091
				'posts_count' => empty($row['count_posts']),
1092
				'cur_topic_approved' => empty($topic) || $row['approved'],
1093
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
1094
			);
1095
1096
			// Load the membergroups allowed, and check permissions.
1097
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
1098
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
1099
1100
			call_integration_hook('integrate_board_info', array(&$board_info, $row));
1101
1102
			if (!empty($modSettings['board_manager_groups']))
1103
			{
1104
				$board_info['groups'] = array_unique(array_merge($board_info['groups'], explode(',', $modSettings['board_manager_groups'])));
1105
				$board_info['deny_groups'] = array_diff($board_info['deny_groups'], explode(',', $modSettings['board_manager_groups']));
1106
			}
1107
1108
			do
1109
			{
1110
				if (!empty($row['id_moderator']))
1111
					$board_info['moderators'][$row['id_moderator']] = array(
1112
						'id' => $row['id_moderator'],
1113
						'name' => $row['real_name'],
1114
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
1115
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
1116
					);
1117
1118
				if (!empty($row['id_moderator_group']))
1119
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
1120
						'id' => $row['id_moderator_group'],
1121
						'name' => $row['group_name'],
1122
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
1123
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
1124
					);
1125
			}
1126
			while ($row = $smcFunc['db_fetch_assoc']($request));
1127
1128
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
1129
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
1130
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
1131
			{
1132
				// Free the previous result
1133
				$smcFunc['db_free_result']($request);
1134
1135
				// @todo why is this using id_topic?
1136
				// @todo Can this get cached?
1137
				$request = $smcFunc['db_query']('', '
1138
					SELECT COUNT(id_topic)
1139
					FROM {db_prefix}topics
1140
					WHERE id_member_started={int:id_member}
1141
						AND approved = {int:unapproved}
1142
						AND id_board = {int:board}',
1143
					array(
1144
						'id_member' => $user_info['id'],
1145
						'unapproved' => 0,
1146
						'board' => $board,
1147
					)
1148
				);
1149
1150
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
1151
			}
1152
1153
			if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1154
			{
1155
				// @todo SLOW?
1156
				if (!empty($topic))
1157
					cache_put_data('topic_board-' . $topic, $board_info, 120);
1158
				cache_put_data('board-' . $board, $board_info, 120);
1159
			}
1160
		}
1161
		else
1162
		{
1163
			// Otherwise the topic is invalid, there are no moderators, etc.
1164
			$board_info = array(
1165
				'moderators' => array(),
1166
				'moderator_groups' => array(),
1167
				'error' => 'exist'
1168
			);
1169
			$topic = null;
1170
			$board = 0;
1171
		}
1172
		$smcFunc['db_free_result']($request);
1173
	}
1174
1175
	if (!empty($topic))
1176
		$_GET['board'] = (int) $board;
1177
1178
	if (!empty($board))
1179
	{
1180
		// Get this into an array of keys for array_intersect
1181
		$moderator_groups = array_keys($board_info['moderator_groups']);
1182
1183
		// Now check if the user is a moderator.
1184
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
1185
1186
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
1187
			$board_info['error'] = 'access';
1188
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
1189
			$board_info['error'] = 'access';
1190
1191
		// Build up the linktree.
1192
		$context['linktree'] = array_merge(
1193
			$context['linktree'],
1194
			array(array(
1195
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
1196
				'name' => $board_info['cat']['name']
1197
			)),
1198
			array_reverse($board_info['parent_boards']),
1199
			array(array(
1200
				'url' => $scripturl . '?board=' . $board . '.0',
1201
				'name' => $board_info['name']
1202
			))
1203
		);
1204
	}
1205
1206
	// Set the template contextual information.
1207
	$context['user']['is_mod'] = &$user_info['is_mod'];
1208
	$context['current_topic'] = $topic;
1209
	$context['current_board'] = $board;
1210
1211
	// No posting in redirection boards!
1212
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1213
		$board_info['error'] = 'post_in_redirect';
1214
1215
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1216
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1217
	{
1218
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1219
		loadPermissions();
1220
		loadTheme();
1221
1222
		$_GET['board'] = '';
1223
		$_GET['topic'] = '';
1224
1225
		// The linktree should not give the game away mate!
1226
		$context['linktree'] = array(
1227
			array(
1228
				'url' => $scripturl,
1229
				'name' => $context['forum_name_html_safe']
1230
			)
1231
		);
1232
1233
		// If it's a prefetching agent or we're requesting an attachment.
1234
		if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach'))
1235
		{
1236
			ob_end_clean();
1237
			send_http_status(403);
1238
			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...
1239
		}
1240
		elseif ($board_info['error'] == 'post_in_redirect')
1241
		{
1242
			// Slightly different error message here...
1243
			fatal_lang_error('cannot_post_redirect', false);
1244
		}
1245
		elseif ($user_info['is_guest'])
1246
		{
1247
			loadLanguage('Errors');
1248
			is_not_guest($txt['topic_gone']);
1249
		}
1250
		else
1251
			fatal_lang_error('topic_gone', false);
1252
	}
1253
1254
	if ($user_info['is_mod'])
1255
		$user_info['groups'][] = 3;
1256
}
1257
1258
/**
1259
 * Load this user's permissions.
1260
 */
1261
function loadPermissions()
1262
{
1263
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir, $cache_enable;
1264
1265
	if ($user_info['is_admin'])
1266
	{
1267
		banPermissions();
1268
		return;
1269
	}
1270
1271
	if (!empty($cache_enable))
1272
	{
1273
		$cache_groups = $user_info['groups'];
1274
		asort($cache_groups);
1275
		$cache_groups = implode(',', $cache_groups);
1276
		// If it's a spider then cache it different.
1277
		if ($user_info['possibly_robot'])
1278
			$cache_groups .= '-spider';
1279
1280
		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...
1281
		{
1282
			list ($user_info['permissions']) = $temp;
1283
			banPermissions();
1284
1285
			return;
1286
		}
1287
		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...
1288
			list ($user_info['permissions'], $removals) = $temp;
1289
	}
1290
1291
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1292
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1293
1294
	if (empty($user_info['permissions']))
1295
	{
1296
		// Get the general permissions.
1297
		$request = $smcFunc['db_query']('', '
1298
			SELECT permission, add_deny
1299
			FROM {db_prefix}permissions
1300
			WHERE id_group IN ({array_int:member_groups})
1301
				' . $spider_restrict,
1302
			array(
1303
				'member_groups' => $user_info['groups'],
1304
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1305
			)
1306
		);
1307
		$removals = array();
1308
		while ($row = $smcFunc['db_fetch_assoc']($request))
1309
		{
1310
			if (empty($row['add_deny']))
1311
				$removals[] = $row['permission'];
1312
			else
1313
				$user_info['permissions'][] = $row['permission'];
1314
		}
1315
		$smcFunc['db_free_result']($request);
1316
1317
		if (isset($cache_groups))
1318
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1319
	}
1320
1321
	// Get the board permissions.
1322
	if (!empty($board))
1323
	{
1324
		// Make sure the board (if any) has been loaded by loadBoard().
1325
		if (!isset($board_info['profile']))
1326
			fatal_lang_error('no_board');
1327
1328
		$request = $smcFunc['db_query']('', '
1329
			SELECT permission, add_deny
1330
			FROM {db_prefix}board_permissions
1331
			WHERE (id_group IN ({array_int:member_groups})
1332
				' . $spider_restrict . ')
1333
				AND id_profile = {int:id_profile}',
1334
			array(
1335
				'member_groups' => $user_info['groups'],
1336
				'id_profile' => $board_info['profile'],
1337
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1338
			)
1339
		);
1340
		while ($row = $smcFunc['db_fetch_assoc']($request))
1341
		{
1342
			if (empty($row['add_deny']))
1343
				$removals[] = $row['permission'];
1344
			else
1345
				$user_info['permissions'][] = $row['permission'];
1346
		}
1347
		$smcFunc['db_free_result']($request);
1348
	}
1349
1350
	// Remove all the permissions they shouldn't have ;).
1351
	if (!empty($modSettings['permission_enable_deny']))
1352
		$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...
1353
1354
	if (isset($cache_groups) && !empty($board) && $cache_enable >= 2)
1355
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1356
1357
	// Banned?  Watch, don't touch..
1358
	banPermissions();
1359
1360
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1361
	if (!$user_info['is_guest'])
1362
	{
1363
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1364
		{
1365
			require_once($sourcedir . '/Subs-Auth.php');
1366
			rebuildModCache();
1367
		}
1368
		else
1369
			$user_info['mod_cache'] = $_SESSION['mc'];
1370
1371
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1372
		// For example this drastically simplifies certain changes to the profile area.
1373
		$user_info['permissions'][] = 'is_not_guest';
1374
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1375
		$user_info['permissions'][] = 'profile_view_own';
1376
		if (in_array('profile_view', $user_info['permissions']))
1377
			$user_info['permissions'][] = 'profile_view_any';
1378
	}
1379
}
1380
1381
/**
1382
 * Loads an array of users' data by ID or member_name.
1383
 *
1384
 * @param array|string $users An array of users by id or name or a single username/id
1385
 * @param bool $is_name Whether $users contains names
1386
 * @param string $set What kind of data to load (normal, profile, minimal)
1387
 * @return array The ids of the members loaded
1388
 */
1389
function loadMemberData($users, $is_name = false, $set = 'normal')
1390
{
1391
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1392
	global $user_info, $cache_enable;
1393
1394
	// Can't just look for no users :P.
1395
	if (empty($users))
1396
		return array();
1397
1398
	// Pass the set value
1399
	$context['loadMemberContext_set'] = $set;
1400
1401
	// Make sure it's an array.
1402
	$users = !is_array($users) ? array($users) : array_unique($users);
1403
	$loaded_ids = array();
1404
1405
	if (!$is_name && !empty($cache_enable) && $cache_enable >= 3)
1406
	{
1407
		$users = array_values($users);
1408
		for ($i = 0, $n = count($users); $i < $n; $i++)
1409
		{
1410
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1411
			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...
1412
				continue;
1413
1414
			$loaded_ids[] = $data['id_member'];
1415
			$user_profile[$data['id_member']] = $data;
1416
			unset($users[$i]);
1417
		}
1418
	}
1419
1420
	// Used by default
1421
	$select_columns = '
1422
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1423
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1424
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1425
			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,
1426
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1427
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1428
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1429
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1430
	$select_tables = '
1431
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1432
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1433
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1434
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1435
1436
	// We add or replace according the the set
1437
	switch ($set)
1438
	{
1439
		case 'normal':
1440
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1441
			break;
1442
		case 'profile':
1443
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1444
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1445
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1446
			break;
1447
		case 'minimal':
1448
			$select_columns = '
1449
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1450
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1451
			$select_tables = '';
1452
			break;
1453
		default:
1454
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1455
	}
1456
1457
	// Allow mods to easily add to the selected member data
1458
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1459
1460
	if (!empty($users))
1461
	{
1462
		// Load the member's data.
1463
		$request = $smcFunc['db_query']('', '
1464
			SELECT' . $select_columns . '
1465
			FROM {db_prefix}members AS mem' . $select_tables . '
1466
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1467
			array(
1468
				'blank_string' => '',
1469
				'users' => $users,
1470
			)
1471
		);
1472
		$new_loaded_ids = array();
1473
		while ($row = $smcFunc['db_fetch_assoc']($request))
1474
		{
1475
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1476
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1477
1478
			// Take care of proxying avatar if required, do this here for maximum reach
1479
			if (!empty($row['avatar']))
1480
				$row['avatar'] = get_proxied_url($row['avatar']);
1481
1482
			// Keep track of the member's normal member group
1483
			$row['primary_group'] = !empty($row['member_group']) ? $row['member_group'] : '';
1484
1485
			if (isset($row['member_ip']))
1486
				$row['member_ip'] = inet_dtop($row['member_ip']);
1487
			if (isset($row['member_ip2']))
1488
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1489
			$row['id_member'] = (int) $row['id_member'];
1490
			$new_loaded_ids[] = $row['id_member'];
1491
			$loaded_ids[] = $row['id_member'];
1492
			$row['options'] = array();
1493
			$user_profile[$row['id_member']] = $row;
1494
		}
1495
		$smcFunc['db_free_result']($request);
1496
	}
1497
1498
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1499
	{
1500
		$request = $smcFunc['db_query']('', '
1501
			SELECT id_member, variable, value
1502
			FROM {db_prefix}themes
1503
			WHERE id_member IN ({array_int:loaded_ids})',
1504
			array(
1505
				'loaded_ids' => $new_loaded_ids,
1506
			)
1507
		);
1508
		while ($row = $smcFunc['db_fetch_assoc']($request))
1509
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1510
		$smcFunc['db_free_result']($request);
1511
	}
1512
1513
	$additional_mods = array();
1514
1515
	// Are any of these users in groups assigned to moderate this board?
1516
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1517
	{
1518
		foreach ($loaded_ids as $a_member)
1519
		{
1520
			if (!empty($user_profile[$a_member]['additional_groups']))
1521
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1522
			else
1523
				$groups = array($user_profile[$a_member]['id_group']);
1524
1525
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1526
1527
			if (!empty($temp))
1528
			{
1529
				$additional_mods[] = $a_member;
1530
			}
1531
		}
1532
	}
1533
1534
	if (!empty($new_loaded_ids) && !empty($cache_enable) && $cache_enable >= 3)
1535
	{
1536
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1537
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1538
	}
1539
1540
	// Are we loading any moderators?  If so, fix their group data...
1541
	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)
1542
	{
1543
		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...
1544
		{
1545
			$request = $smcFunc['db_query']('', '
1546
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1547
				FROM {db_prefix}membergroups
1548
				WHERE id_group = {int:moderator_group}
1549
				LIMIT 1',
1550
				array(
1551
					'moderator_group' => 3,
1552
				)
1553
			);
1554
			$row = $smcFunc['db_fetch_assoc']($request);
1555
			$smcFunc['db_free_result']($request);
1556
1557
			cache_put_data('moderator_group_info', $row, 480);
1558
		}
1559
1560
		foreach ($temp_mods as $id)
1561
		{
1562
			// By popular demand, don't show admins or global moderators as moderators.
1563
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1564
				$user_profile[$id]['member_group'] = $row['member_group'];
1565
1566
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1567
			if (!empty($row['icons']))
1568
				$user_profile[$id]['icons'] = $row['icons'];
1569
			if (!empty($row['member_group_color']))
1570
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1571
		}
1572
	}
1573
1574
	return $loaded_ids;
1575
}
1576
1577
/**
1578
 * Loads the user's basic values... meant for template/theme usage.
1579
 *
1580
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1581
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1582
 * @return boolean|array  False if the data wasn't loaded or the loaded data.
1583
 * @throws Exception
1584
 */
1585
function loadMemberContext($user, $display_custom_fields = false)
1586
{
1587
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1588
	global $context, $modSettings, $settings, $smcFunc;
1589
	static $already_loaded_custom_fields = array();
1590
	static $loadedLanguages = array();
1591
1592
	// If this person's data is already loaded, skip it.
1593
	if (!empty($memberContext[$user]) && !empty($already_loaded_custom_fields[$user]) >= $display_custom_fields)
1594
		return $memberContext[$user];
1595
1596
	// We can't load guests or members not loaded by loadMemberData()!
1597
	if ($user == 0)
1598
		return false;
1599
	if (!isset($user_profile[$user]))
1600
	{
1601
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1602
		return false;
1603
	}
1604
1605
	// Well, it's loaded now anyhow.
1606
	$profile = $user_profile[$user];
1607
1608
	// Censor everything.
1609
	censorText($profile['signature']);
1610
	censorText($profile['personal_text']);
1611
1612
	// Set things up to be used before hand.
1613
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1614
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1615
1616
	$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...
1617
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1618
	// Setup the buddy status here (One whole in_array call saved :P)
1619
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1620
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1621
1622
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1623
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1624
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1625
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1626
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1627
	else //not set, bye bye
1628
		$group_icon_url = '';
1629
1630
	// These minimal values are always loaded
1631
	$memberContext[$user] = array(
1632
		'username' => $profile['member_name'],
1633
		'name' => $profile['real_name'],
1634
		'id' => $profile['id_member'],
1635
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1636
		'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>',
1637
		'email' => $profile['email_address'],
1638
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1639
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1640
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1641
	);
1642
1643
	// If the set isn't minimal then load the monstrous array.
1644
	if ($context['loadMemberContext_set'] != 'minimal')
1645
	{
1646
		// Go the extra mile and load the user's native language name.
1647
		if (empty($loadedLanguages))
1648
			$loadedLanguages = getLanguages();
1649
1650
		// Figure out the new time offset.
1651
		if (!empty($profile['timezone']))
1652
		{
1653
			// Get the offsets from UTC for the server, then for the user.
1654
			$tz_system = new DateTimeZone(@date_default_timezone_get());
1655
			$tz_user = new DateTimeZone($profile['timezone']);
1656
			$time_system = new DateTime('now', $tz_system);
1657
			$time_user = new DateTime('now', $tz_user);
1658
			$profile['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
1659
		}
1660
1661
		else
1662
		{
1663
			// !!! Compatibility.
1664
			$profile['time_offset'] = empty($profile['time_offset']) ? 0 : $profile['time_offset'];
1665
		}
1666
1667
		$memberContext[$user] += array(
1668
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1669
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1670
			'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>',
1671
			'is_buddy' => $profile['buddy'],
1672
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1673
			'buddies' => $buddy_list,
1674
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1675
			'blurb' => $profile['personal_text'],
1676
			'website' => array(
1677
				'title' => $profile['website_title'],
1678
				'url' => $profile['website_url'],
1679
			),
1680
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1681
			'signature' => $profile['signature'],
1682
			'real_posts' => $profile['posts'],
1683
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1684
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1685
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
1686
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1687
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1688
			'online' => array(
1689
				'is_online' => $profile['is_online'],
1690
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1691
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1692
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1693
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1694
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1695
			),
1696
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1697
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1698
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1699
			'options' => $profile['options'],
1700
			'is_guest' => false,
1701
			'primary_group' => $profile['primary_group'],
1702
			'group' => $profile['member_group'],
1703
			'group_color' => $profile['member_group_color'],
1704
			'group_id' => $profile['id_group'],
1705
			'post_group' => $profile['post_group'],
1706
			'post_group_color' => $profile['post_group_color'],
1707
			'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]),
1708
			'warning' => $profile['warning'],
1709
			'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' : (''))),
1710
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1711
			'custom_fields' => array(),
1712
		);
1713
	}
1714
1715
	// If the set isn't minimal then load their avatar as well.
1716
	if ($context['loadMemberContext_set'] != 'minimal')
1717
	{
1718
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1719
		{
1720
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1721
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1722
			else
1723
				$image = get_gravatar_url($profile['email_address']);
1724
		}
1725
		else
1726
		{
1727
			// So it's stored in the member table?
1728
			if (!empty($profile['avatar']))
1729
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1730
1731
			elseif (!empty($profile['filename']))
1732
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1733
1734
			// Right... no avatar...use the default one
1735
			else
1736
				$image = $modSettings['avatar_url'] . '/default.png';
1737
		}
1738
1739
		if (!empty($image))
1740
			$memberContext[$user]['avatar'] = array(
1741
				'name' => $profile['avatar'],
1742
				'image' => '<img class="avatar" src="' . $image . '" alt="">',
1743
				'href' => $image,
1744
				'url' => $image,
1745
			);
1746
	}
1747
1748
	// Are we also loading the members custom fields into context?
1749
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1750
	{
1751
		$memberContext[$user]['custom_fields'] = array();
1752
1753
		if (!isset($context['display_fields']))
1754
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1755
1756
		foreach ($context['display_fields'] as $custom)
1757
		{
1758
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1759
				continue;
1760
1761
			$value = $profile['options'][$custom['col_name']];
1762
1763
			$fieldOptions = array();
1764
			$currentKey = 0;
1765
1766
			// Create a key => value array for multiple options fields
1767
			if (!empty($custom['options']))
1768
				foreach ($custom['options'] as $k => $v)
1769
				{
1770
					$fieldOptions[] = $v;
1771
					if (empty($currentKey))
1772
						$currentKey = $v == $value ? $k : 0;
1773
				}
1774
1775
			// BBC?
1776
			if ($custom['bbc'])
1777
				$value = parse_bbc($value);
1778
1779
			// ... or checkbox?
1780
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1781
				$value = $value ? $txt['yes'] : $txt['no'];
1782
1783
			// Enclosing the user input within some other text?
1784
			if (!empty($custom['enclose']))
1785
				$value = strtr($custom['enclose'], array(
1786
					'{SCRIPTURL}' => $scripturl,
1787
					'{IMAGES_URL}' => $settings['images_url'],
1788
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1789
					'{INPUT}' => $value,
1790
					'{KEY}' => $currentKey,
1791
				));
1792
1793
			$memberContext[$user]['custom_fields'][] = array(
1794
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1795
				'col_name' => $custom['col_name'],
1796
				'value' => un_htmlspecialchars($value),
1797
				'raw' => $profile['options'][$custom['col_name']],
1798
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1799
			);
1800
		}
1801
	}
1802
1803
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1804
1805
	$already_loaded_custom_fields[$user] = !empty($already_loaded_custom_fields[$user]) | $display_custom_fields;
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
1806
1807
	return $memberContext[$user];
1808
}
1809
1810
/**
1811
 * Loads the user's custom profile fields
1812
 *
1813
 * @param integer|array $users A single user ID or an array of user IDs
1814
 * @param string|array $params Either a string or an array of strings with profile field names
1815
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1816
 */
1817
function loadMemberCustomFields($users, $params)
1818
{
1819
	global $smcFunc, $txt, $scripturl, $settings;
1820
1821
	// Do not waste my time...
1822
	if (empty($users) || empty($params))
1823
		return false;
1824
1825
	// Make sure it's an array.
1826
	$users = !is_array($users) ? array($users) : array_unique($users);
1827
	$params = !is_array($params) ? array($params) : array_unique($params);
1828
	$return = array();
1829
1830
	$request = $smcFunc['db_query']('', '
1831
		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,
1832
		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
1833
		FROM {db_prefix}themes AS t
1834
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1835
		WHERE id_member IN ({array_int:loaded_ids})
1836
			AND variable IN ({array_string:params})
1837
		ORDER BY field_order',
1838
		array(
1839
			'loaded_ids' => $users,
1840
			'params' => $params,
1841
		)
1842
	);
1843
1844
	while ($row = $smcFunc['db_fetch_assoc']($request))
1845
	{
1846
		$fieldOptions = array();
1847
		$currentKey = 0;
1848
1849
		// Create a key => value array for multiple options fields
1850
		if (!empty($row['field_options']))
1851
			foreach (explode(',', $row['field_options']) as $k => $v)
1852
			{
1853
				$fieldOptions[] = $v;
1854
				if (empty($currentKey))
1855
					$currentKey = $v == $row['value'] ? $k : 0;
1856
			}
1857
1858
		// BBC?
1859
		if (!empty($row['bbc']))
1860
			$row['value'] = parse_bbc($row['value']);
1861
1862
		// ... or checkbox?
1863
		elseif (isset($row['type']) && $row['type'] == 'check')
1864
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1865
1866
		// Enclosing the user input within some other text?
1867
		if (!empty($row['enclose']))
1868
			$row['value'] = strtr($row['enclose'], array(
1869
				'{SCRIPTURL}' => $scripturl,
1870
				'{IMAGES_URL}' => $settings['images_url'],
1871
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1872
				'{INPUT}' => un_htmlspecialchars($row['value']),
1873
				'{KEY}' => $currentKey,
1874
			));
1875
1876
		// Send a simple array if there is just 1 param
1877
		if (count($params) == 1)
1878
			$return[$row['id_member']] = $row;
1879
1880
		// More than 1? knock yourself out...
1881
		else
1882
		{
1883
			if (!isset($return[$row['id_member']]))
1884
				$return[$row['id_member']] = array();
1885
1886
			$return[$row['id_member']][$row['variable']] = $row;
1887
		}
1888
	}
1889
1890
	$smcFunc['db_free_result']($request);
1891
1892
	return !empty($return) ? $return : false;
1893
}
1894
1895
/**
1896
 * Loads information about what browser the user is viewing with and places it in $context
1897
 *  - uses the class from {@link Class-BrowserDetect.php}
1898
 */
1899
function detectBrowser()
1900
{
1901
	// Load the current user's browser of choice
1902
	$detector = new browser_detector;
1903
	$detector->detectBrowser();
1904
}
1905
1906
/**
1907
 * Are we using this browser?
1908
 *
1909
 * Wrapper function for detectBrowser
1910
 *
1911
 * @param string $browser The browser we are checking for.
1912
 * @return bool Whether or not the current browser is what we're looking for
1913
 */
1914
function isBrowser($browser)
1915
{
1916
	global $context;
1917
1918
	// Don't know any browser!
1919
	if (empty($context['browser']))
1920
		detectBrowser();
1921
1922
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1923
}
1924
1925
/**
1926
 * Load a theme, by ID.
1927
 *
1928
 * @param int $id_theme The ID of the theme to load
1929
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1930
 */
1931
function loadTheme($id_theme = 0, $initialize = true)
1932
{
1933
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1934
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1935
	global $context, $settings, $options, $sourcedir, $smcFunc, $language, $board, $cache_enable;
1936
1937
	if (empty($id_theme))
1938
	{
1939
		// The theme was specified by the board.
1940
		if (!empty($board_info['theme']))
1941
			$id_theme = $board_info['theme'];
1942
		// The theme is the forum's default.
1943
		else
1944
			$id_theme = $modSettings['theme_guests'];
1945
1946
		// Sometimes the user can choose their own theme.
1947
		if (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))
1948
		{
1949
			// The theme was specified by REQUEST.
1950
			if (!empty($_REQUEST['theme']) && (allowedTo('admin_forum') || in_array($_REQUEST['theme'], explode(',', $modSettings['knownThemes']))))
1951
			{
1952
				$id_theme = (int) $_REQUEST['theme'];
1953
				$_SESSION['id_theme'] = $id_theme;
1954
			}
1955
			// The theme was specified by REQUEST... previously.
1956
			elseif (!empty($_SESSION['id_theme']))
1957
				$id_theme = (int) $_SESSION['id_theme'];
1958
			// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1959
			elseif (!empty($user_info['theme']))
1960
				$id_theme = $user_info['theme'];
1961
		}
1962
1963
		// Verify the id_theme... no foul play.
1964
		// Always allow the board specific theme, if they are overriding.
1965
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1966
			$id_theme = $board_info['theme'];
1967
		elseif (!empty($modSettings['enableThemes']))
1968
		{
1969
			$themes = explode(',', $modSettings['enableThemes']);
1970
			if (!in_array($id_theme, $themes))
1971
				$id_theme = $modSettings['theme_guests'];
1972
			else
1973
				$id_theme = (int) $id_theme;
1974
		}
1975
	}
1976
1977
	// Allow mod authors the option to override the theme id for custom page themes
1978
	call_integration_hook('integrate_pre_load_theme', array(&$id_theme));
1979
1980
	// We already load the basic stuff?
1981
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme)
1982
	{
1983
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1984
1985
		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...
1986
		{
1987
			$themeData = $temp;
1988
			$flag = true;
1989
		}
1990
		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...
1991
			$themeData = $temp + array($member => array());
1992
		else
1993
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1994
1995
		if (empty($flag))
1996
		{
1997
			// Load variables from the current or default theme, global or this user's.
1998
			$result = $smcFunc['db_query']('', '
1999
				SELECT variable, value, id_member, id_theme
2000
				FROM {db_prefix}themes
2001
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
2002
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)') . '
2003
				ORDER BY id_theme asc',
2004
				array(
2005
					'id_theme' => $id_theme,
2006
					'id_member' => $member,
2007
				)
2008
			);
2009
			// Pick between $settings and $options depending on whose data it is.
2010
			foreach ($smcFunc['db_fetch_all']($result) as $row)
2011
			{
2012
				// There are just things we shouldn't be able to change as members.
2013
				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')))
2014
					continue;
2015
2016
				// If this is the theme_dir of the default theme, store it.
2017
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
2018
					$themeData[0]['default_' . $row['variable']] = $row['value'];
2019
2020
				// If this isn't set yet, is a theme option, or is not the default theme..
2021
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
2022
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
2023
			}
2024
			$smcFunc['db_free_result']($result);
2025
2026
			if (!empty($themeData[-1]))
2027
				foreach ($themeData[-1] as $k => $v)
2028
				{
2029
					if (!isset($themeData[$member][$k]))
2030
						$themeData[$member][$k] = $v;
2031
				}
2032
2033
			if (!empty($cache_enable) && $cache_enable >= 2)
2034
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
2035
			// Only if we didn't already load that part of the cache...
2036
			elseif (!isset($temp))
2037
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
2038
		}
2039
2040
		$settings = $themeData[0];
2041
		$options = $themeData[$member];
2042
2043
		$settings['theme_id'] = $id_theme;
2044
2045
		$settings['actual_theme_url'] = $settings['theme_url'];
2046
		$settings['actual_images_url'] = $settings['images_url'];
2047
		$settings['actual_theme_dir'] = $settings['theme_dir'];
2048
2049
		$settings['template_dirs'] = array();
2050
		// This theme first.
2051
		$settings['template_dirs'][] = $settings['theme_dir'];
2052
2053
		// Based on theme (if there is one).
2054
		if (!empty($settings['base_theme_dir']))
2055
			$settings['template_dirs'][] = $settings['base_theme_dir'];
2056
2057
		// Lastly the default theme.
2058
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
2059
			$settings['template_dirs'][] = $settings['default_theme_dir'];
2060
	}
2061
2062
	if (!$initialize)
2063
		return;
2064
2065
	// Perhaps we've changed the agreement or privacy policy? Only redirect if:
2066
	// 1. They're not a guest or admin
2067
	// 2. This isn't called from SSI
2068
	// 3. This isn't an XML request
2069
	// 4. They're not trying to do any of the following actions:
2070
	// 4a. View or accept the agreement and/or policy
2071
	// 4b. Login or logout
2072
	// 4c. Get a feed (RSS, ATOM, etc.)
2073
	$agreement_actions = array(
2074
		'agreement' => true,
2075
		'acceptagreement' => true,
2076
		'login2' => true,
2077
		'logintfa' => true,
2078
		'logout' => true,
2079
		'pm' => array('sa' => array('popup')),
2080
		'profile' => array('area' => array('popup', 'alerts_popup')),
2081
		'xmlhttp' => true,
2082
		'.xml' => true,
2083
	);
2084
	if (empty($user_info['is_guest']) && empty($user_info['is_admin']) && SMF != 'SSI' && !isset($_REQUEST['xml']) && !is_filtered_request($agreement_actions, 'action'))
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
2085
	{
2086
		require_once($sourcedir . '/Agreement.php');
2087
		$can_accept_agreement = !empty($modSettings['requireAgreement']) && canRequireAgreement();
2088
		$can_accept_privacy_policy = !empty($modSettings['requirePolicyAgreement']) && canRequirePrivacyPolicy();
2089
2090
		if ($can_accept_agreement || $can_accept_privacy_policy)
2091
			redirectexit('action=agreement');
2092
	}
2093
2094
	// Check to see if we're forcing SSL
2095
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
2096
		!httpsOn() && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
2097
	{
2098
		if (isset($_GET['sslRedirect']))
2099
		{
2100
			loadLanguage('Errors');
2101
			fatal_lang_error('login_ssl_required', false);
2102
		}
2103
2104
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
2105
	}
2106
2107
	// Check to see if they're accessing it from the wrong place.
2108
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
2109
	{
2110
		$detected_url = httpsOn() ? 'https://' : 'http://';
2111
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
2112
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
2113
		if ($temp != '/')
2114
			$detected_url .= $temp;
2115
	}
2116
	if (isset($detected_url) && $detected_url != $boardurl)
2117
	{
2118
		// Try #1 - check if it's in a list of alias addresses.
2119
		if (!empty($modSettings['forum_alias_urls']))
2120
		{
2121
			$aliases = explode(',', $modSettings['forum_alias_urls']);
2122
2123
			foreach ($aliases as $alias)
2124
			{
2125
				// Rip off all the boring parts, spaces, etc.
2126
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
2127
					$do_fix = true;
2128
			}
2129
		}
2130
2131
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
2132
		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...
2133
		{
2134
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
2135
			if (empty($_GET))
2136
				redirectexit('wwwRedirect');
2137
			else
2138
			{
2139
				$k = key($_GET);
2140
				$v = current($_GET);
2141
2142
				if ($k != 'wwwRedirect')
2143
					redirectexit('wwwRedirect;' . $k . '=' . $v);
2144
			}
2145
		}
2146
2147
		// #3 is just a check for SSL...
2148
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
2149
			$do_fix = true;
2150
2151
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
2152
		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...
2153
		{
2154
			// Caching is good ;).
2155
			$oldurl = $boardurl;
2156
2157
			// Fix $boardurl and $scripturl.
2158
			$boardurl = $detected_url;
2159
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
2160
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
2161
2162
			// Fix the theme urls...
2163
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
2164
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
2165
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
2166
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
2167
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
2168
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
2169
2170
			// And just a few mod settings :).
2171
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
2172
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
2173
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
2174
2175
			// Clean up after loadBoard().
2176
			if (isset($board_info['moderators']))
2177
			{
2178
				foreach ($board_info['moderators'] as $k => $dummy)
2179
				{
2180
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
2181
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
2182
				}
2183
			}
2184
			foreach ($context['linktree'] as $k => $dummy)
2185
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
2186
		}
2187
	}
2188
	// Set up the contextual user array.
2189
	if (!empty($user_info))
2190
	{
2191
		$context['user'] = array(
2192
			'id' => $user_info['id'],
2193
			'is_logged' => !$user_info['is_guest'],
2194
			'is_guest' => &$user_info['is_guest'],
2195
			'is_admin' => &$user_info['is_admin'],
2196
			'is_mod' => &$user_info['is_mod'],
2197
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
2198
			'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'])))),
2199
			'name' => $user_info['username'],
2200
			'language' => $user_info['language'],
2201
			'email' => $user_info['email'],
2202
			'ignoreusers' => $user_info['ignoreusers'],
2203
		);
2204
		if (!$context['user']['is_guest'])
2205
			$context['user']['name'] = $user_info['name'];
2206
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
2207
			$context['user']['name'] = $txt['guest_title'];
2208
2209
		// Determine the current smiley set.
2210
		$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
2211
		$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'];
2212
		$context['user']['smiley_set'] = $user_info['smiley_set'];
2213
	}
2214
	else
2215
	{
2216
		// What to do when there is no $user_info (e.g., an error very early in the login process)
2217
		$context['user'] = array(
2218
			'id' => -1,
2219
			'is_logged' => false,
2220
			'is_guest' => true,
2221
			'is_mod' => false,
2222
			'can_mod' => false,
2223
			'name' => $txt['guest_title'],
2224
			'language' => $language,
2225
			'email' => '',
2226
			'ignoreusers' => array(),
2227
		);
2228
		// Note we should stuff $user_info with some guest values also...
2229
		$user_info = array(
2230
			'id' => 0,
2231
			'is_guest' => true,
2232
			'is_admin' => false,
2233
			'is_mod' => false,
2234
			'username' => $txt['guest_title'],
2235
			'language' => $language,
2236
			'email' => '',
2237
			'smiley_set' => '',
2238
			'permissions' => array(),
2239
			'groups' => array(),
2240
			'ignoreusers' => array(),
2241
			'possibly_robot' => true,
2242
			'time_offset' => 0,
2243
			'time_format' => $modSettings['time_format'],
2244
		);
2245
	}
2246
2247
	// Some basic information...
2248
	if (!isset($context['html_headers']))
2249
		$context['html_headers'] = '';
2250
	if (!isset($context['javascript_files']))
2251
		$context['javascript_files'] = array();
2252
	if (!isset($context['css_files']))
2253
		$context['css_files'] = array();
2254
	if (!isset($context['css_header']))
2255
		$context['css_header'] = array();
2256
	if (!isset($context['javascript_inline']))
2257
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2258
	if (!isset($context['javascript_vars']))
2259
		$context['javascript_vars'] = array();
2260
2261
	$context['login_url'] = $scripturl . '?action=login2';
2262
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2263
	$context['session_var'] = $_SESSION['session_var'];
2264
	$context['session_id'] = $_SESSION['session_value'];
2265
	$context['forum_name'] = $mbname;
2266
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2267
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2268
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2269
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2270
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2271
	if (isset($modSettings['load_average']))
2272
		$context['load_average'] = $modSettings['load_average'];
2273
2274
	// Detect the browser. This is separated out because it's also used in attachment downloads
2275
	detectBrowser();
2276
2277
	// Set the top level linktree up.
2278
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2279
	if (empty($context['linktree']))
2280
		$context['linktree'] = array();
2281
	array_unshift($context['linktree'], array(
2282
		'url' => $scripturl,
2283
		'name' => $context['forum_name_html_safe']
2284
	));
2285
2286
	// This allows sticking some HTML on the page output - useful for controls.
2287
	$context['insert_after_template'] = '';
2288
2289
	if (!isset($txt))
2290
		$txt = array();
2291
2292
	$simpleActions = array(
2293
		'findmember',
2294
		'helpadmin',
2295
		'printpage',
2296
		'spellcheck',
2297
	);
2298
2299
	// Parent action => array of areas
2300
	$simpleAreas = array(
2301
		'profile' => array('popup', 'alerts_popup',),
2302
	);
2303
2304
	// Parent action => array of subactions
2305
	$simpleSubActions = array(
2306
		'pm' => array('popup',),
2307
		'signup' => array('usernamecheck'),
2308
	);
2309
2310
	// Extra params like ;preview ;js, etc.
2311
	$extraParams = array(
2312
		'preview',
2313
		'splitjs',
2314
	);
2315
2316
	// Actions that specifically uses XML output.
2317
	$xmlActions = array(
2318
		'quotefast',
2319
		'jsmodify',
2320
		'xmlhttp',
2321
		'post2',
2322
		'suggest',
2323
		'stats',
2324
		'notifytopic',
2325
		'notifyboard',
2326
	);
2327
2328
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2329
2330
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2331
		(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2332
		(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2333
2334
	// See if theres any extra param to check.
2335
	$requiresXML = false;
2336
	foreach ($extraParams as $key => $extra)
2337
		if (isset($_REQUEST[$extra]))
2338
			$requiresXML = true;
2339
2340
	// Output is fully XML, so no need for the index template.
2341
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2342
	{
2343
		loadLanguage('index+Modifications');
2344
		loadTemplate('Xml');
2345
		$context['template_layers'] = array();
2346
	}
2347
2348
	// These actions don't require the index template at all.
2349
	elseif (!empty($context['simple_action']))
2350
	{
2351
		loadLanguage('index+Modifications');
2352
		$context['template_layers'] = array();
2353
	}
2354
2355
	else
2356
	{
2357
		// Custom templates to load, or just default?
2358
		if (isset($settings['theme_templates']))
2359
			$templates = explode(',', $settings['theme_templates']);
2360
		else
2361
			$templates = array('index');
2362
2363
		// Load each template...
2364
		foreach ($templates as $template)
2365
			loadTemplate($template);
2366
2367
		// ...and attempt to load their associated language files.
2368
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2369
		loadLanguage($required_files, '', false);
2370
2371
		// Custom template layers?
2372
		if (isset($settings['theme_layers']))
2373
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2374
		else
2375
			$context['template_layers'] = array('html', 'body');
2376
	}
2377
2378
	// Initialize the theme.
2379
	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

2379
	loadSubTemplate('init', /** @scrutinizer ignore-type */ 'ignore');
Loading history...
2380
2381
	// Allow overriding the board wide time/number formats.
2382
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2383
		$user_info['time_format'] = $txt['time_format'];
2384
2385
	// Set the character set from the template.
2386
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2387
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2388
2389
	// Guests may still need a name.
2390
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2391
		$context['user']['name'] = $txt['guest_title'];
2392
2393
	// Any theme-related strings that need to be loaded?
2394
	if (!empty($settings['require_theme_strings']))
2395
		loadLanguage('ThemeStrings', '', false);
2396
2397
	// Make a special URL for the language.
2398
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2399
2400
	// And of course, let's load the default CSS file.
2401
	loadCSSFile('index.css', array('minimize' => true, 'order_pos' => 1), 'smf_index');
2402
2403
	// Here is my luvly Responsive CSS
2404
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true, 'order_pos' => 9000), 'smf_responsive');
2405
2406
	if ($context['right_to_left'])
2407
		loadCSSFile('rtl.css', array('order_pos' => 4000), 'smf_rtl');
2408
2409
	// We allow theme variants, because we're cool.
2410
	$context['theme_variant'] = '';
2411
	$context['theme_variant_url'] = '';
2412
	if (!empty($settings['theme_variants']))
2413
	{
2414
		// Overriding - for previews and that ilk.
2415
		if (!empty($_REQUEST['variant']))
2416
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2417
		// User selection?
2418
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2419
			$context['theme_variant'] = !empty($_SESSION['id_variant']) && in_array($_SESSION['id_variant'], $settings['theme_variants']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) && in_array($options['theme_variant'], $settings['theme_variants']) ? $options['theme_variant'] : '');
2420
		// If not a user variant, select the default.
2421
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2422
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2423
2424
		// Do this to keep things easier in the templates.
2425
		$context['theme_variant'] = '_' . $context['theme_variant'];
2426
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2427
2428
		if (!empty($context['theme_variant']))
2429
		{
2430
			loadCSSFile('index' . $context['theme_variant'] . '.css', array('order_pos' => 300), 'smf_index' . $context['theme_variant']);
2431
			if ($context['right_to_left'])
2432
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array('order_pos' => 4200), 'smf_rtl' . $context['theme_variant']);
2433
		}
2434
	}
2435
2436
	// Let's be compatible with old themes!
2437
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2438
		$context['template_layers'] = array('main');
2439
2440
	$context['tabindex'] = 1;
2441
2442
	// Compatibility.
2443
	if (!isset($settings['theme_version']))
2444
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2445
2446
	// Default JS variables for use in every theme
2447
	$context['javascript_vars'] = array(
2448
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2449
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2450
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2451
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2452
		'smf_smiley_sets' => '"' . $modSettings['smiley_sets_known'] . '"',
2453
		'smf_smiley_sets_default' => '"' . $modSettings['smiley_sets_default'] . '"',
2454
		'smf_scripturl' => '"' . $scripturl . '"',
2455
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2456
		'smf_charset' => '"' . $context['character_set'] . '"',
2457
		'smf_session_id' => '"' . $context['session_id'] . '"',
2458
		'smf_session_var' => '"' . $context['session_var'] . '"',
2459
		'smf_member_id' => $context['user']['id'],
2460
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2461
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2462
		'banned_text' => JavaScriptEscape(sprintf($txt['your_ban'], $context['user']['name'])),
2463
		'smf_txt_expand' => JavaScriptEscape($txt['code_expand']),
2464
		'smf_txt_shrink' => JavaScriptEscape($txt['code_shrink']),
2465
		'smf_quote_expand' => !empty($modSettings['quote_expand']) ? $modSettings['quote_expand'] : 'false',
2466
	);
2467
2468
	// Add the JQuery library to the list of files to load.
2469
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2470
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2471
2472
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2473
		loadJavaScriptFile('jquery-' . JQUERY_VERSION . '.min.js', array('seed' => false), 'smf_jquery');
2474
2475
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2476
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2477
2478
	// Auto loading? template_javascript() will take care of the local half of this.
2479
	else
2480
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2481
2482
	// Queue our JQuery plugins!
2483
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2484
	if (!$user_info['is_guest'])
2485
	{
2486
		loadJavaScriptFile('jquery.custom-scrollbar.js', array('minimize' => true), 'smf_jquery_scrollbar');
2487
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2488
	}
2489
2490
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2491
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2492
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2493
2494
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2495
	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())
2496
	{
2497
		if (isBrowser('possibly_robot'))
2498
		{
2499
			// @todo Maybe move this somewhere better?!
2500
			require_once($sourcedir . '/ScheduledTasks.php');
2501
2502
			// What to do, what to do?!
2503
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2504
				AutoTask();
2505
			else
2506
				ReduceMailQueue();
2507
		}
2508
		else
2509
		{
2510
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2511
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
2512
2513
			addInlineJavaScript('
2514
		function smfAutoTask()
2515
		{
2516
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2517
		}
2518
		window.setTimeout("smfAutoTask();", 1);');
2519
		}
2520
	}
2521
2522
	// And we should probably trigger the cron too.
2523
	if (empty($modSettings['cron_is_real_cron']))
2524
	{
2525
		$ts = time();
2526
		$ts -= $ts % 15;
2527
		addInlineJavaScript('
2528
	function triggerCron()
2529
	{
2530
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2531
	}
2532
	window.setTimeout(triggerCron, 1);', true);
2533
	}
2534
2535
	// Filter out the restricted boards from the linktree
2536
	if (!$user_info['is_admin'] && !empty($board))
2537
	{
2538
		foreach ($context['linktree'] as $k => $element)
2539
		{
2540
			if (!empty($element['groups']) &&
2541
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2542
					(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2543
			{
2544
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2545
				$context['linktree'][$k]['extra_before'] = '<i>';
2546
				$context['linktree'][$k]['extra_after'] = '</i>';
2547
				unset($context['linktree'][$k]['url']);
2548
			}
2549
		}
2550
	}
2551
2552
	// Any files to include at this point?
2553
	if (!empty($modSettings['integrate_theme_include']))
2554
	{
2555
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2556
		foreach ($theme_includes as $include)
2557
		{
2558
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2559
			if (file_exists($include))
2560
				require_once($include);
2561
		}
2562
	}
2563
2564
	// Call load theme integration functions.
2565
	call_integration_hook('integrate_load_theme');
2566
2567
	// We are ready to go.
2568
	$context['theme_loaded'] = true;
2569
}
2570
2571
/**
2572
 * Load a template - if the theme doesn't include it, use the default.
2573
 * What this function does:
2574
 *  - loads a template file with the name template_name from the current, default, or base theme.
2575
 *  - detects a wrong default theme directory and tries to work around it.
2576
 *
2577
 * @uses template_include() to include the file.
2578
 * @param string $template_name The name of the template to load
2579
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2580
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2581
 * @return boolean Whether or not the template was loaded
2582
 */
2583
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2584
{
2585
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
2586
2587
	// Do any style sheets first, cause we're easy with those.
2588
	if (!empty($style_sheets))
2589
	{
2590
		if (!is_array($style_sheets))
2591
			$style_sheets = array($style_sheets);
2592
2593
		foreach ($style_sheets as $sheet)
2594
			loadCSSFile($sheet . '.css', array(), $sheet);
2595
	}
2596
2597
	// No template to load?
2598
	if ($template_name === false)
0 ignored issues
show
introduced by
The condition $template_name === false is always false.
Loading history...
2599
		return true;
2600
2601
	$loaded = false;
2602
	foreach ($settings['template_dirs'] as $template_dir)
2603
	{
2604
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2605
		{
2606
			$loaded = true;
2607
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2608
			break;
2609
		}
2610
	}
2611
2612
	if ($loaded)
0 ignored issues
show
introduced by
The condition $loaded is always false.
Loading history...
2613
	{
2614
		if ($db_show_debug === true)
2615
			$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 2602. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2616
2617
		// If they have specified an initialization function for this template, go ahead and call it now.
2618
		if (function_exists('template_' . $template_name . '_init'))
2619
			call_user_func('template_' . $template_name . '_init');
2620
	}
2621
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2622
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2623
	{
2624
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2625
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2626
2627
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2628
		{
2629
			loadLanguage('Errors');
2630
			echo '
2631
<div class="alert errorbox">
2632
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2633
</div>';
2634
		}
2635
2636
		loadTemplate($template_name);
2637
	}
2638
	// Cause an error otherwise.
2639
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2640
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2641
	elseif ($fatal)
2642
		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...
2643
	else
2644
		return false;
2645
}
2646
2647
/**
2648
 * Load a sub-template.
2649
 * What it does:
2650
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2651
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2652
 *	for debugging purposes.
2653
 *
2654
 * @todo get rid of reading $_REQUEST directly
2655
 *
2656
 * @param string $sub_template_name The name of the sub-template to load
2657
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2658
 */
2659
function loadSubTemplate($sub_template_name, $fatal = false)
2660
{
2661
	global $context, $txt, $db_show_debug;
2662
2663
	if ($db_show_debug === true)
2664
		$context['debug']['sub_templates'][] = $sub_template_name;
2665
2666
	// Figure out what the template function is named.
2667
	$theme_function = 'template_' . $sub_template_name;
2668
	if (function_exists($theme_function))
2669
		$theme_function();
2670
	elseif ($fatal === false)
2671
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2672
	elseif ($fatal !== 'ignore')
0 ignored issues
show
introduced by
The condition $fatal !== 'ignore' is always true.
Loading history...
2673
		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...
2674
2675
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2676
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2677
	{
2678
		echo '
2679
<div class="warningbox">---- ', $sub_template_name, ' ends ----</div>';
2680
	}
2681
}
2682
2683
/**
2684
 * Add a CSS file for output later
2685
 *
2686
 * @param string $fileName The name of the file to load
2687
 * @param array $params An array of parameters
2688
 * Keys are the following:
2689
 * 	- ['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
2690
 * 	- ['default_theme'] (true/false): force use of default theme url
2691
 * 	- ['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
2692
 *  - ['validate'] (true/false): if true script will validate the local file exists
2693
 *  - ['rtl'] (string): additional file to load in RTL mode
2694
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2695
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2696
 *  - ['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
2697
 *  - ['attributes'] array extra attributes to add to the element
2698
 * @param string $id An ID to stick on the end of the filename for caching purposes
2699
 */
2700
function loadCSSFile($fileName, $params = array(), $id = '')
2701
{
2702
	global $settings, $context, $modSettings;
2703
2704
	if (empty($context['css_files_order']))
2705
		$context['css_files_order'] = array();
2706
2707
	$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'], '?') : '');
2708
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2709
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2710
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : true;
2711
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2712
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2713
	$params['order_pos'] = isset($params['order_pos']) ? (int) $params['order_pos'] : 3000;
2714
2715
	// If this is an external file, automatically set this to false.
2716
	if (!empty($params['external']))
2717
		$params['minimize'] = false;
2718
2719
	// Account for shorthand like admin.css?alp21 filenames
2720
	$id = empty($id) ? strtr(str_replace('.css', '', basename($fileName)), '?', '_') : $id;
2721
	$fileName = str_replace(pathinfo($fileName, PATHINFO_EXTENSION), strtok(pathinfo($fileName, PATHINFO_EXTENSION), '?'), $fileName);
2722
2723
	// Is this a local file?
2724
	if (empty($params['external']))
2725
	{
2726
		// Are we validating the the file exists?
2727
		if (!empty($params['validate']) && ($mtime = @filemtime($settings[$themeRef . '_dir'] . '/css/' . $fileName)) === false)
2728
		{
2729
			// Maybe the default theme has it?
2730
			if ($themeRef === 'theme' && !$params['force_current'] && ($mtime = @filemtime($settings['default_theme_dir'] . '/css/' . $fileName) !== false))
2731
			{
2732
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName;
2733
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName;
2734
			}
2735
			else
2736
			{
2737
				$fileUrl = false;
2738
				$filePath = false;
2739
			}
2740
		}
2741
		else
2742
		{
2743
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName;
2744
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName;
2745
			$mtime = @filemtime($filePath);
2746
		}
2747
	}
2748
	// An external file doesn't have a filepath. Mock one for simplicity.
2749
	else
2750
	{
2751
		$fileUrl = $fileName;
2752
		$filePath = $fileName;
2753
	}
2754
2755
	$mtime = empty($mtime) ? 0 : $mtime;
2756
2757
	// Add it to the array for use in the template
2758
	if (!empty($fileName) && !empty($fileUrl))
2759
	{
2760
		// find a free number/position
2761
		while (isset($context['css_files_order'][$params['order_pos']]))
2762
			$params['order_pos']++;
2763
		$context['css_files_order'][$params['order_pos']] = $id;
2764
2765
		$context['css_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params, 'mtime' => $mtime);
2766
	}
2767
2768
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2769
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2770
2771
	if ($mtime > $modSettings['browser_cache'])
2772
		updateSettings(array('browser_cache' => $mtime));
2773
}
2774
2775
/**
2776
 * Add a block of inline css code to be executed later
2777
 *
2778
 * - only use this if you have to, generally external css files are better, but for very small changes
2779
 *   or for scripts that require help from PHP/whatever, this can be useful.
2780
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2781
 *
2782
 * @param string $css Some css code
2783
 * @return void|bool Adds the CSS to the $context['css_header'] array or returns if no CSS is specified
2784
 */
2785
function addInlineCss($css)
2786
{
2787
	global $context;
2788
2789
	// Gotta add something...
2790
	if (empty($css))
2791
		return false;
2792
2793
	$context['css_header'][] = $css;
2794
}
2795
2796
/**
2797
 * Add a Javascript file for output later
2798
 *
2799
 * @param string $fileName The name of the file to load
2800
 * @param array $params An array of parameter info
2801
 * Keys are the following:
2802
 * 	- ['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
2803
 * 	- ['default_theme'] (true/false): force use of default theme url
2804
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2805
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2806
 *	default theme if not found in the current theme
2807
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2808
 *  - ['validate'] (true/false): if true script will validate the local file exists
2809
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2810
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2811
 *  - ['attributes'] array extra attributes to add to the element
2812
 *
2813
 * @param string $id An ID to stick on the end of the filename
2814
 */
2815
function loadJavaScriptFile($fileName, $params = array(), $id = '')
2816
{
2817
	global $settings, $context, $modSettings;
2818
2819
	$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'], '?') : '');
2820
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2821
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2822
	$params['async'] = isset($params['async']) ? $params['async'] : false;
2823
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2824
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2825
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2826
2827
	// If this is an external file, automatically set this to false.
2828
	if (!empty($params['external']))
2829
		$params['minimize'] = false;
2830
2831
	// Account for shorthand like admin.js?alp21 filenames
2832
	$id = empty($id) ? strtr(str_replace('.js', '', basename($fileName)), '?', '_') : $id;
2833
	$fileName = str_replace(pathinfo($fileName, PATHINFO_EXTENSION), strtok(pathinfo($fileName, PATHINFO_EXTENSION), '?'), $fileName);
2834
2835
	// Is this a local file?
2836
	if (empty($params['external']))
2837
	{
2838
		// Are we validating it exists on disk?
2839
		if (!empty($params['validate']) && ($mtime = @filemtime($settings[$themeRef . '_dir'] . '/scripts/' . $fileName)) === false)
2840
		{
2841
			// Can't find it in this theme, how about the default?
2842
			if ($themeRef === 'theme' && !$params['force_current'] && ($mtime = @filemtime($settings['default_theme_dir'] . '/scripts/' . $fileName)) !== false)
2843
			{
2844
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName;
2845
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName;
2846
			}
2847
			else
2848
			{
2849
				$fileUrl = false;
2850
				$filePath = false;
2851
			}
2852
		}
2853
		else
2854
		{
2855
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName;
2856
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName;
2857
			$mtime = @filemtime($filePath);
2858
		}
2859
	}
2860
	// An external file doesn't have a filepath. Mock one for simplicity.
2861
	else
2862
	{
2863
		$fileUrl = $fileName;
2864
		$filePath = $fileName;
2865
	}
2866
2867
	$mtime = empty($mtime) ? 0 : $mtime;
2868
2869
	// Add it to the array for use in the template
2870
	if (!empty($fileName) && !empty($fileUrl))
2871
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params, 'mtime' => $mtime);
2872
2873
	if ($mtime > $modSettings['browser_cache'])
2874
		updateSettings(array('browser_cache' => $mtime));
2875
}
2876
2877
/**
2878
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2879
 * Cleaner and easier (for modders) than to use the function below.
2880
 *
2881
 * @param string $key The key for this variable
2882
 * @param string $value The value
2883
 * @param bool $escape Whether or not to escape the value
2884
 */
2885
function addJavaScriptVar($key, $value, $escape = false)
2886
{
2887
	global $context;
2888
2889
	if (!empty($key) && (!empty($value) || $value === '0'))
2890
		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
2891
}
2892
2893
/**
2894
 * Add a block of inline Javascript code to be executed later
2895
 *
2896
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2897
 *   or for scripts that require help from PHP/whatever, this can be useful.
2898
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2899
 *
2900
 * @param string $javascript Some JS code
2901
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2902
 * @return void|bool Adds the code to one of the $context['javascript_inline'] arrays or returns if no JS was specified
2903
 */
2904
function addInlineJavaScript($javascript, $defer = false)
2905
{
2906
	global $context;
2907
2908
	if (empty($javascript))
2909
		return false;
2910
2911
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2912
}
2913
2914
/**
2915
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2916
 *
2917
 * @param string $template_name The name of a template file
2918
 * @param string $lang A specific language to load this file from
2919
 * @param bool $fatal Whether to die with an error if it can't be loaded
2920
 * @param bool $force_reload Whether to load the file again if it's already loaded
2921
 * @return string The language actually loaded.
2922
 */
2923
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2924
{
2925
	global $user_info, $language, $settings, $context, $modSettings;
2926
	global $db_show_debug, $sourcedir, $txt, $birthdayEmails, $txtBirthdayEmails;
2927
	static $already_loaded = array();
2928
2929
	// Default to the user's language.
2930
	if ($lang == '')
2931
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2932
2933
	// Do we want the English version of language file as fallback?
2934
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2935
		loadLanguage($template_name, 'english', false);
2936
2937
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2938
		return $lang;
2939
2940
	// Make sure we have $settings - if not we're in trouble and need to find it!
2941
	if (empty($settings['default_theme_dir']))
2942
	{
2943
		require_once($sourcedir . '/ScheduledTasks.php');
2944
		loadEssentialThemeData();
2945
	}
2946
2947
	// What theme are we in?
2948
	$theme_name = basename($settings['theme_url']);
2949
	if (empty($theme_name))
2950
		$theme_name = 'unknown';
2951
2952
	// For each file open it up and write it out!
2953
	foreach (explode('+', $template_name) as $template)
2954
	{
2955
		// Obviously, the current theme is most important to check.
2956
		$attempts = array(
2957
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2958
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2959
		);
2960
2961
		// Do we have a base theme to worry about?
2962
		if (isset($settings['base_theme_dir']))
2963
		{
2964
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2965
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2966
		}
2967
2968
		// Fall back on the default theme if necessary.
2969
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2970
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2971
2972
		// Fall back on the English language if none of the preferred languages can be found.
2973
		if (!in_array('english', array($lang, $language)))
2974
		{
2975
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2976
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2977
		}
2978
2979
		// Try to find the language file.
2980
		$found = false;
2981
		foreach ($attempts as $k => $file)
2982
		{
2983
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2984
			{
2985
				// Include it!
2986
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2987
2988
				// Note that we found it.
2989
				$found = true;
2990
2991
				// setlocale is required for basename() & pathinfo() to work properly on the selected language
2992
				if (!empty($txt['lang_locale']) && !empty($modSettings['global_character_set']))
2993
					setlocale(LC_CTYPE, $txt['lang_locale'] . '.' . $modSettings['global_character_set']);
2994
2995
				break;
2996
			}
2997
		}
2998
2999
		// That couldn't be found!  Log the error, but *try* to continue normally.
3000
		if (!$found && $fatal)
3001
		{
3002
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
3003
			break;
3004
		}
3005
3006
		// For the sake of backward compatibility
3007
		if (!empty($txt['emails']))
3008
		{
3009
			foreach ($txt['emails'] as $key => $value)
3010
			{
3011
				$txt[$key . '_subject'] = $value['subject'];
3012
				$txt[$key . '_body'] = $value['body'];
3013
			}
3014
			$txt['emails'] = array();
3015
		}
3016
		// For sake of backward compatibility: $birthdayEmails is supposed to be
3017
		// empty in a normal install. If it isn't it means the forum is using
3018
		// something "old" (it may be the translation, it may be a mod) and this
3019
		// code (like the piece above) takes care of converting it to the new format
3020
		if (!empty($birthdayEmails))
3021
		{
3022
			foreach ($birthdayEmails as $key => $value)
3023
			{
3024
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
3025
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
3026
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
3027
			}
3028
			$birthdayEmails = array();
3029
		}
3030
	}
3031
3032
	// Keep track of what we're up to soldier.
3033
	if ($db_show_debug === true)
3034
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
3035
3036
	// Remember what we have loaded, and in which language.
3037
	$already_loaded[$template_name] = $lang;
3038
3039
	// Return the language actually loaded.
3040
	return $lang;
3041
}
3042
3043
/**
3044
 * Get all parent boards (requires first parent as parameter)
3045
 * It finds all the parents of id_parent, and that board itself.
3046
 * Additionally, it detects the moderators of said boards.
3047
 *
3048
 * @param int $id_parent The ID of the parent board
3049
 * @return array An array of information about the boards found.
3050
 */
3051
function getBoardParents($id_parent)
3052
{
3053
	global $scripturl, $smcFunc;
3054
3055
	// First check if we have this cached already.
3056
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
3057
	{
3058
		$boards = array();
3059
		$original_parent = $id_parent;
3060
3061
		// Loop while the parent is non-zero.
3062
		while ($id_parent != 0)
3063
		{
3064
			$result = $smcFunc['db_query']('', '
3065
				SELECT
3066
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
3067
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
3068
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
3069
				FROM {db_prefix}boards AS b
3070
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
3071
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
3072
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
3073
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
3074
				WHERE b.id_board = {int:board_parent}',
3075
				array(
3076
					'board_parent' => $id_parent,
3077
				)
3078
			);
3079
			// In the EXTREMELY unlikely event this happens, give an error message.
3080
			if ($smcFunc['db_num_rows']($result) == 0)
3081
				fatal_lang_error('parent_not_found', 'critical');
3082
			while ($row = $smcFunc['db_fetch_assoc']($result))
3083
			{
3084
				if (!isset($boards[$row['id_board']]))
3085
				{
3086
					$id_parent = $row['id_parent'];
3087
					$boards[$row['id_board']] = array(
3088
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
3089
						'name' => $row['name'],
3090
						'level' => $row['child_level'],
3091
						'groups' => explode(',', $row['member_groups']),
3092
						'deny_groups' => explode(',', $row['deny_member_groups']),
3093
						'moderators' => array(),
3094
						'moderator_groups' => array()
3095
					);
3096
				}
3097
				// If a moderator exists for this board, add that moderator for all children too.
3098
				if (!empty($row['id_moderator']))
3099
					foreach ($boards as $id => $dummy)
3100
					{
3101
						$boards[$id]['moderators'][$row['id_moderator']] = array(
3102
							'id' => $row['id_moderator'],
3103
							'name' => $row['real_name'],
3104
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
3105
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
3106
						);
3107
					}
3108
3109
				// If a moderator group exists for this board, add that moderator group for all children too
3110
				if (!empty($row['id_moderator_group']))
3111
					foreach ($boards as $id => $dummy)
3112
					{
3113
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
3114
							'id' => $row['id_moderator_group'],
3115
							'name' => $row['group_name'],
3116
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
3117
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
3118
						);
3119
					}
3120
			}
3121
			$smcFunc['db_free_result']($result);
3122
		}
3123
3124
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
3125
	}
3126
3127
	return $boards;
3128
}
3129
3130
/**
3131
 * Attempt to reload our known languages.
3132
 * It will try to choose only utf8 or non-utf8 languages.
3133
 *
3134
 * @param bool $use_cache Whether or not to use the cache
3135
 * @return array An array of information about available languages
3136
 */
3137
function getLanguages($use_cache = true)
3138
{
3139
	global $context, $smcFunc, $settings, $modSettings, $cache_enable;
3140
3141
	// Either we don't use the cache, or its expired.
3142
	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...
3143
	{
3144
		// If we don't have our ucwords function defined yet, let's load the settings data.
3145
		if (empty($smcFunc['ucwords']))
3146
			reloadSettings();
3147
3148
		// If we don't have our theme information yet, let's get it.
3149
		if (empty($settings['default_theme_dir']))
3150
			loadTheme(0, false);
3151
3152
		// Default language directories to try.
3153
		$language_directories = array(
3154
			$settings['default_theme_dir'] . '/languages',
3155
		);
3156
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
3157
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
3158
3159
		// We possibly have a base theme directory.
3160
		if (!empty($settings['base_theme_dir']))
3161
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
3162
3163
		// Remove any duplicates.
3164
		$language_directories = array_unique($language_directories);
3165
3166
		foreach ($language_directories as $language_dir)
3167
		{
3168
			// Can't look in here... doesn't exist!
3169
			if (!file_exists($language_dir))
3170
				continue;
3171
3172
			$dir = dir($language_dir);
3173
			while ($entry = $dir->read())
3174
			{
3175
				// Look for the index language file... For good measure skip any "index.language-utf8.php" files
3176
				if (!preg_match('~^index\.((?:.(?!-utf8))+)\.php$~', $entry, $matches))
3177
					continue;
3178
3179
				$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
3180
3181
				if (($spos = strpos($langName, ' ')) !== false)
3182
					$langName = substr($langName, 0, ++$spos) . '(' . substr($langName, $spos) . ')';
3183
3184
				// Get the line we need.
3185
				$fp = @fopen($language_dir . '/' . $entry, 'r');
3186
3187
				// Yay!
3188
				if ($fp)
3189
				{
3190
					while (($line = fgets($fp)) !== false)
3191
					{
3192
						if (strpos($line, '$txt[\'native_name\']') === false)
3193
							continue;
3194
3195
						preg_match('~\$txt\[\'native_name\'\]\s*=\s*\'([^\']+)\';~', $line, $matchNative);
3196
3197
						// Set the language's name.
3198
						if (!empty($matchNative) && !empty($matchNative[1]))
3199
						{
3200
							// Don't mislabel the language if the translator missed this one.
3201
							if ($langName !== 'English' && $matchNative[1] === 'English')
3202
								break;
3203
3204
							$langName = un_htmlspecialchars($matchNative[1]);
3205
							break;
3206
						}
3207
					}
3208
3209
					fclose($fp);
3210
				}
3211
3212
				// Build this language entry.
3213
				$context['languages'][$matches[1]] = array(
3214
					'name' => $langName,
3215
					'selected' => false,
3216
					'filename' => $matches[1],
3217
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
3218
				);
3219
			}
3220
			$dir->close();
3221
		}
3222
3223
		// Avoid confusion when we have more than one English variant installed.
3224
		// Honestly, our default English version should always have been called "English (US)"
3225
		if (substr_count(implode(' ', array_keys($context['languages'])), 'english') > 1 && $context['languages']['english']['name'] === 'English')
3226
			$context['languages']['english']['name'] = 'English (US)';
3227
3228
		// Let's cash in on this deal.
3229
		if (!empty($cache_enable))
3230
			cache_put_data('known_languages', $context['languages'], !empty($cache_enable) && $cache_enable < 1 ? 86400 : 3600);
3231
	}
3232
3233
	return $context['languages'];
3234
}
3235
3236
/**
3237
 * Replace all vulgar words with respective proper words. (substring or whole words..)
3238
 * What this function does:
3239
 *  - it censors the passed string.
3240
 *  - if the theme setting allow_no_censored is on, and the theme option
3241
 *	show_no_censored is enabled, does not censor, unless force is also set.
3242
 *  - it caches the list of censored words to reduce parsing.
3243
 *
3244
 * @param string &$text The text to censor
3245
 * @param bool $force Whether to censor the text regardless of settings
3246
 * @return string The censored text
3247
 */
3248
function censorText(&$text, $force = false)
3249
{
3250
	global $modSettings, $options, $txt;
3251
	static $censor_vulgar = null, $censor_proper;
3252
3253
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
3254
		return $text;
3255
3256
	// If they haven't yet been loaded, load them.
3257
	if ($censor_vulgar == null)
3258
	{
3259
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
3260
		$censor_proper = explode("\n", $modSettings['censor_proper']);
3261
3262
		// Quote them for use in regular expressions.
3263
		if (!empty($modSettings['censorWholeWord']))
3264
		{
3265
			$charset = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
3266
3267
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
3268
			{
3269
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
3270
3271
				// Use the faster \b if we can, or something more complex if we can't
3272
				$boundary_before = preg_match('/^\w/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?<![\p{L}\p{M}\p{N}_])' : '(?<!\w)');
3273
				$boundary_after = preg_match('/\w$/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?![\p{L}\p{M}\p{N}_])' : '(?!\w)');
3274
3275
				$censor_vulgar[$i] = '/' . $boundary_before . $censor_vulgar[$i] . $boundary_after . '/' . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ($charset === 'UTF-8' ? 'u' : '');
3276
			}
3277
		}
3278
	}
3279
3280
	// Censoring isn't so very complicated :P.
3281
	if (empty($modSettings['censorWholeWord']))
3282
	{
3283
		$func = !empty($modSettings['censorIgnoreCase']) ? 'str_ireplace' : 'str_replace';
3284
		$text = $func($censor_vulgar, $censor_proper, $text);
3285
	}
3286
	else
3287
		$text = preg_replace($censor_vulgar, $censor_proper, $text);
3288
3289
	return $text;
3290
}
3291
3292
/**
3293
 * Load the template/language file using require
3294
 * 	- loads the template or language file specified by filename.
3295
 * 	- uses eval unless disableTemplateEval is enabled.
3296
 * 	- outputs a parse error if the file did not exist or contained errors.
3297
 * 	- attempts to detect the error and line, and show detailed information.
3298
 *
3299
 * @param string $filename The name of the file to include
3300
 * @param bool $once If true only includes the file once (like include_once)
3301
 */
3302
function template_include($filename, $once = false)
3303
{
3304
	global $context, $txt, $scripturl, $modSettings;
3305
	global $boardurl, $boarddir;
3306
	global $maintenance, $mtitle, $mmessage;
3307
	static $templates = array();
3308
3309
	// We want to be able to figure out any errors...
3310
	@ini_set('track_errors', '1');
3311
3312
	// Don't include the file more than once, if $once is true.
3313
	if ($once && in_array($filename, $templates))
3314
		return;
3315
	// Add this file to the include list, whether $once is true or not.
3316
	else
3317
		$templates[] = $filename;
3318
3319
	$file_found = file_exists($filename);
3320
3321
	if ($once && $file_found)
3322
		require_once($filename);
3323
	elseif ($file_found)
3324
		require($filename);
3325
3326
	if ($file_found !== true)
3327
	{
3328
		ob_end_clean();
3329
		if (!empty($modSettings['enableCompressedOutput']))
3330
			@ob_start('ob_gzhandler');
3331
		else
3332
			ob_start();
3333
3334
		if (isset($_GET['debug']))
3335
			header('content-type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3336
3337
		// Don't cache error pages!!
3338
		header('expires: Mon, 26 Jul 1997 05:00:00 GMT');
3339
		header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3340
		header('cache-control: no-cache');
3341
3342
		if (!isset($txt['template_parse_error']))
3343
		{
3344
			$txt['template_parse_error'] = 'Template Parse Error!';
3345
			$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>.';
3346
			$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="%2$s%1$s" class="extern">accessing the file directly</a>.<br><br>You may want to try to <a href="javascript:location.reload();">refresh this page</a> or <a href="%3$s?theme=1">use the default theme</a>.';
3347
			$txt['template_parse_errmsg'] = 'Unfortunately more information is not available at this time as to exactly what is wrong.';
3348
		}
3349
3350
		// First, let's get the doctype and language information out of the way.
3351
		echo '<!DOCTYPE html>
3352
<html', !empty($context['right_to_left']) ? ' dir="rtl"' : '', '>
3353
	<head>';
3354
		if (isset($context['character_set']))
3355
			echo '
3356
		<meta charset="', $context['character_set'], '">';
3357
3358
		if (!empty($maintenance) && !allowedTo('admin_forum'))
3359
			echo '
3360
		<title>', $mtitle, '</title>
3361
	</head>
3362
	<body>
3363
		<h3>', $mtitle, '</h3>
3364
		', $mmessage, '
3365
	</body>
3366
</html>';
3367
		elseif (!allowedTo('admin_forum'))
3368
			echo '
3369
		<title>', $txt['template_parse_error'], '</title>
3370
	</head>
3371
	<body>
3372
		<h3>', $txt['template_parse_error'], '</h3>
3373
		', $txt['template_parse_error_message'], '
3374
	</body>
3375
</html>';
3376
		else
3377
		{
3378
			$error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3379
			$error_array = error_get_last();
3380
			if (empty($error) && ini_get('track_errors') && !empty($error_array))
3381
				$error = $error_array['message'];
3382
			if (empty($error))
3383
				$error = $txt['template_parse_errmsg'];
3384
3385
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
3386
3387
			echo '
3388
		<title>', $txt['template_parse_error'], '</title>
3389
	</head>
3390
	<body>
3391
		<h3>', $txt['template_parse_error'], '</h3>
3392
		', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')), $boardurl, $scripturl);
3393
3394
			if (!empty($error))
3395
				echo '
3396
		<hr>
3397
3398
		<div style="margin: 0 20px;"><pre>', strtr(strtr($error, array('<strong>' . $boarddir => '<strong>...', '<strong>' . strtr($boarddir, '\\', '/') => '<strong>...')), '\\', '/'), '</pre></div>';
3399
3400
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
3401
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
3402
			{
3403
				$data = file($filename);
3404
				$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

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

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

3868
				/** @scrutinizer ignore-call */ 
3869
    loadLanguageErrors();
Loading history...
3869
				log_error($txt['secret_auth_missing'], 'critical');
3870
			}
3871
		}
3872
	}
3873
3874
3875
	return $auth_secret;
3876
}
3877
3878
?>