Passed
Pull Request — release-2.1 (#6101)
by Jon
03:54
created

loadMinUserSettings()   C

Complexity

Conditions 11
Paths 196

Size

Total Lines 77
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 7 Features 1
Metric Value
cc 11
eloc 47
c 10
b 7
f 1
nc 196
nop 1
dl 0
loc 77
rs 6.5166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

Loading history...
88
	}
89
90
	$modSettings['cache_enable'] = $cache_enable;
91
92
	// Used to force browsers to download fresh CSS and JavaScript when necessary
93
	$modSettings['browser_cache'] = !empty($modSettings['browser_cache']) ? (int) $modSettings['browser_cache'] : 0;
94
	$context['browser_cache'] = '?' . preg_replace('~\W~', '', strtolower(SMF_FULL_VERSION)) . '_' . $modSettings['browser_cache'];
95
96
	// Disable image proxy if we don't have SSL enabled
97
	if (empty($modSettings['force_ssl']))
98
		$image_proxy_enabled = false;
99
100
	// UTF-8 ?
101
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
102
	$context['utf8'] = $utf8;
103
104
	// Set a list of common functions.
105
	$ent_list = '&(?:#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . '|quot|amp|lt|gt|nbsp);';
106
	$ent_check = empty($modSettings['disableEntityCheck']) ? function($string)
107
		{
108
			$string = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $string);
109
			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
	// Check to see if we're forcing SSL
2066
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
2067
		!httpsOn() && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
2068
	{
2069
		if (isset($_GET['sslRedirect']))
2070
		{
2071
			loadLanguage('Errors');
2072
			fatal_lang_error('login_ssl_required', false);
2073
		}
2074
2075
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
2076
	}
2077
2078
	// Check to see if they're accessing it from the wrong place.
2079
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
2080
	{
2081
		$detected_url = httpsOn() ? 'https://' : 'http://';
2082
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
2083
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
2084
		if ($temp != '/')
2085
			$detected_url .= $temp;
2086
	}
2087
	if (isset($detected_url) && $detected_url != $boardurl)
2088
	{
2089
		// Try #1 - check if it's in a list of alias addresses.
2090
		if (!empty($modSettings['forum_alias_urls']))
2091
		{
2092
			$aliases = explode(',', $modSettings['forum_alias_urls']);
2093
2094
			foreach ($aliases as $alias)
2095
			{
2096
				// Rip off all the boring parts, spaces, etc.
2097
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
2098
					$do_fix = true;
2099
			}
2100
		}
2101
2102
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
2103
		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...
2104
		{
2105
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
2106
			if (empty($_GET))
2107
				redirectexit('wwwRedirect');
2108
			else
2109
			{
2110
				$k = key($_GET);
2111
				$v = current($_GET);
2112
2113
				if ($k != 'wwwRedirect')
2114
					redirectexit('wwwRedirect;' . $k . '=' . $v);
2115
			}
2116
		}
2117
2118
		// #3 is just a check for SSL...
2119
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
2120
			$do_fix = true;
2121
2122
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
2123
		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...
2124
		{
2125
			// Caching is good ;).
2126
			$oldurl = $boardurl;
2127
2128
			// Fix $boardurl and $scripturl.
2129
			$boardurl = $detected_url;
2130
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
2131
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
2132
2133
			// Fix the theme urls...
2134
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
2135
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
2136
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
2137
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
2138
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
2139
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
2140
2141
			// And just a few mod settings :).
2142
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
2143
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
2144
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
2145
2146
			// Clean up after loadBoard().
2147
			if (isset($board_info['moderators']))
2148
			{
2149
				foreach ($board_info['moderators'] as $k => $dummy)
2150
				{
2151
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
2152
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
2153
				}
2154
			}
2155
			foreach ($context['linktree'] as $k => $dummy)
2156
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
2157
		}
2158
	}
2159
	// Set up the contextual user array.
2160
	if (!empty($user_info))
2161
	{
2162
		$context['user'] = array(
2163
			'id' => $user_info['id'],
2164
			'is_logged' => !$user_info['is_guest'],
2165
			'is_guest' => &$user_info['is_guest'],
2166
			'is_admin' => &$user_info['is_admin'],
2167
			'is_mod' => &$user_info['is_mod'],
2168
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
2169
			'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'])))),
2170
			'name' => $user_info['username'],
2171
			'language' => $user_info['language'],
2172
			'email' => $user_info['email'],
2173
			'ignoreusers' => $user_info['ignoreusers'],
2174
		);
2175
		if (!$context['user']['is_guest'])
2176
			$context['user']['name'] = $user_info['name'];
2177
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
2178
			$context['user']['name'] = $txt['guest_title'];
2179
2180
		// Determine the current smiley set.
2181
		$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
2182
		$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'];
2183
		$context['user']['smiley_set'] = $user_info['smiley_set'];
2184
	}
2185
	else
2186
	{
2187
		// What to do when there is no $user_info (e.g., an error very early in the login process)
2188
		$context['user'] = array(
2189
			'id' => -1,
2190
			'is_logged' => false,
2191
			'is_guest' => true,
2192
			'is_mod' => false,
2193
			'can_mod' => false,
2194
			'name' => $txt['guest_title'],
2195
			'language' => $language,
2196
			'email' => '',
2197
			'ignoreusers' => array(),
2198
		);
2199
		// Note we should stuff $user_info with some guest values also...
2200
		$user_info = array(
2201
			'id' => 0,
2202
			'is_guest' => true,
2203
			'is_admin' => false,
2204
			'is_mod' => false,
2205
			'username' => $txt['guest_title'],
2206
			'language' => $language,
2207
			'email' => '',
2208
			'smiley_set' => '',
2209
			'permissions' => array(),
2210
			'groups' => array(),
2211
			'ignoreusers' => array(),
2212
			'possibly_robot' => true,
2213
			'time_offset' => 0,
2214
			'time_format' => $modSettings['time_format'],
2215
		);
2216
	}
2217
2218
	// Some basic information...
2219
	if (!isset($context['html_headers']))
2220
		$context['html_headers'] = '';
2221
	if (!isset($context['javascript_files']))
2222
		$context['javascript_files'] = array();
2223
	if (!isset($context['css_files']))
2224
		$context['css_files'] = array();
2225
	if (!isset($context['css_header']))
2226
		$context['css_header'] = array();
2227
	if (!isset($context['javascript_inline']))
2228
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2229
	if (!isset($context['javascript_vars']))
2230
		$context['javascript_vars'] = array();
2231
2232
	$context['login_url'] = $scripturl . '?action=login2';
2233
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2234
	$context['session_var'] = $_SESSION['session_var'];
2235
	$context['session_id'] = $_SESSION['session_value'];
2236
	$context['forum_name'] = $mbname;
2237
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2238
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2239
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2240
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2241
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2242
	if (isset($modSettings['load_average']))
2243
		$context['load_average'] = $modSettings['load_average'];
2244
2245
	// Detect the browser. This is separated out because it's also used in attachment downloads
2246
	detectBrowser();
2247
2248
	// Set the top level linktree up.
2249
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2250
	if (empty($context['linktree']))
2251
		$context['linktree'] = array();
2252
	array_unshift($context['linktree'], array(
2253
		'url' => $scripturl,
2254
		'name' => $context['forum_name_html_safe']
2255
	));
2256
2257
	// This allows sticking some HTML on the page output - useful for controls.
2258
	$context['insert_after_template'] = '';
2259
2260
	if (!isset($txt))
2261
		$txt = array();
2262
2263
	$simpleActions = array(
2264
		'findmember',
2265
		'helpadmin',
2266
		'printpage',
2267
		'spellcheck',
2268
	);
2269
2270
	// Parent action => array of areas
2271
	$simpleAreas = array(
2272
		'profile' => array('popup', 'alerts_popup',),
2273
	);
2274
2275
	// Parent action => array of subactions
2276
	$simpleSubActions = array(
2277
		'pm' => array('popup',),
2278
		'signup' => array('usernamecheck'),
2279
	);
2280
2281
	// Extra params like ;preview ;js, etc.
2282
	$extraParams = array(
2283
		'preview',
2284
		'splitjs',
2285
	);
2286
2287
	// Actions that specifically uses XML output.
2288
	$xmlActions = array(
2289
		'quotefast',
2290
		'jsmodify',
2291
		'xmlhttp',
2292
		'post2',
2293
		'suggest',
2294
		'stats',
2295
		'notifytopic',
2296
		'notifyboard',
2297
	);
2298
2299
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2300
2301
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2302
		(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2303
		(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2304
2305
	// See if theres any extra param to check.
2306
	$requiresXML = false;
2307
	foreach ($extraParams as $key => $extra)
2308
		if (isset($_REQUEST[$extra]))
2309
			$requiresXML = true;
2310
2311
	// Output is fully XML, so no need for the index template.
2312
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2313
	{
2314
		loadLanguage('index+Modifications');
2315
		loadTemplate('Xml');
2316
		$context['template_layers'] = array();
2317
	}
2318
2319
	// These actions don't require the index template at all.
2320
	elseif (!empty($context['simple_action']))
2321
	{
2322
		loadLanguage('index+Modifications');
2323
		$context['template_layers'] = array();
2324
	}
2325
2326
	else
2327
	{
2328
		// Custom templates to load, or just default?
2329
		if (isset($settings['theme_templates']))
2330
			$templates = explode(',', $settings['theme_templates']);
2331
		else
2332
			$templates = array('index');
2333
2334
		// Load each template...
2335
		foreach ($templates as $template)
2336
			loadTemplate($template);
2337
2338
		// ...and attempt to load their associated language files.
2339
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2340
		loadLanguage($required_files, '', false);
2341
2342
		// Custom template layers?
2343
		if (isset($settings['theme_layers']))
2344
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2345
		else
2346
			$context['template_layers'] = array('html', 'body');
2347
	}
2348
2349
	// Initialize the theme.
2350
	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

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

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